diff --git a/.github/workflows/check-qldoc.yml b/.github/workflows/check-qldoc.yml index 85249fbd92a..6cb99154aa4 100644 --- a/.github/workflows/check-qldoc.yml +++ b/.github/workflows/check-qldoc.yml @@ -26,9 +26,8 @@ jobs: shell: bash run: | EXIT_CODE=0 - # TODO: remove the swift exception from the regex when we fix generated QLdoc # TODO: remove the shared exception from the regex when coverage of qlpacks without dbschemes is supported - changed_lib_packs="$(git diff --name-only --diff-filter=ACMRT HEAD^ HEAD | { grep -Po '^(?!(swift|shared))[a-z]*/ql/lib' || true; } | sort -u)" + changed_lib_packs="$(git diff --name-only --diff-filter=ACMRT HEAD^ HEAD | { grep -Po '^(?!(shared))[a-z]*/ql/lib' || true; } | sort -u)" for pack_dir in ${changed_lib_packs}; do lang="${pack_dir%/ql/lib}" codeql generate library-doc-coverage --output="${RUNNER_TEMP}/${lang}-current.txt" --dir="${pack_dir}" diff --git a/config/identical-files.json b/config/identical-files.json index 05ef9698ed5..39dd63aae5d 100644 --- a/config/identical-files.json +++ b/config/identical-files.json @@ -1,6 +1,26 @@ { "DataFlow Java/C++/C#/Go/Python/Ruby/Swift": [ + "java/ql/lib/semmle/code/java/dataflow/internal/DataFlow.qll", + "cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlow.qll", + "cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlow.qll", + "csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlow.qll", + "go/ql/lib/semmle/go/dataflow/internal/DataFlow.qll", + "python/ql/lib/semmle/python/dataflow/new/internal/DataFlow.qll", + "ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlow.qll", + "swift/ql/lib/codeql/swift/dataflow/internal/DataFlow.qll" + ], + "DataFlowImpl Java/C++/C#/Go/Python/Ruby/Swift": [ "java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl.qll", + "cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll", + "cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll", + "csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll", + "go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl.qll", + "python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl.qll", + "ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl.qll", + "swift/ql/lib/codeql/swift/dataflow/internal/DataFlowImpl.qll" + ], + "DataFlow Java/C++/C#/Go/Python/Ruby/Swift Legacy Configuration": [ + "java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl1.qll", "java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl2.qll", "java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl3.qll", "java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl4.qll", @@ -8,34 +28,34 @@ "java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl6.qll", "java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForSerializability.qll", "java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForOnActivityResult.qll", - "cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll", + "cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl1.qll", "cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll", "cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll", "cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll", "cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll", - "cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll", + "cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl1.qll", "cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll", "cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll", "cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll", - "csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll", + "csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl1.qll", "csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll", "csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll", "csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll", "csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll", "csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImplForContentDataFlow.qll", - "go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl.qll", + "go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl1.qll", "go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl2.qll", "go/ql/lib/semmle/go/dataflow/internal/DataFlowImplForStringsNewReplacer.qll", - "python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl.qll", + "python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl1.qll", "python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl2.qll", "python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl3.qll", "python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl4.qll", "python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImplForRegExp.qll", - "ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl.qll", + "ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl1.qll", "ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl2.qll", "ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplForHttpClientLibraries.qll", "ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplForPathname.qll", - "swift/ql/lib/codeql/swift/dataflow/internal/DataFlowImpl.qll" + "swift/ql/lib/codeql/swift/dataflow/internal/DataFlowImpl1.qll" ], "DataFlow Java/C++/C#/Go/Python/Ruby/Swift Common": [ "java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll", @@ -47,7 +67,17 @@ "ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplCommon.qll", "swift/ql/lib/codeql/swift/dataflow/internal/DataFlowImplCommon.qll" ], - "TaintTracking::Configuration Java/C++/C#/Go/Python/Ruby/Swift": [ + "TaintTracking Java/C++/C#/Go/Python/Ruby/Swift": [ + "cpp/ql/lib/semmle/code/cpp/dataflow/internal/tainttracking1/TaintTracking.qll", + "cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/tainttracking1/TaintTracking.qll", + "csharp/ql/lib/semmle/code/csharp/dataflow/internal/tainttracking1/TaintTracking.qll", + "go/ql/lib/semmle/go/dataflow/internal/tainttracking1/TaintTracking.qll", + "java/ql/lib/semmle/code/java/dataflow/internal/tainttracking1/TaintTracking.qll", + "python/ql/lib/semmle/python/dataflow/new/internal/tainttracking1/TaintTracking.qll", + "ruby/ql/lib/codeql/ruby/dataflow/internal/tainttracking1/TaintTracking.qll", + "swift/ql/lib/codeql/swift/dataflow/internal/tainttracking1/TaintTracking.qll" + ], + "TaintTracking Legacy Configuration Java/C++/C#/Go/Python/Ruby/Swift": [ "cpp/ql/lib/semmle/code/cpp/dataflow/internal/tainttracking1/TaintTrackingImpl.qll", "cpp/ql/lib/semmle/code/cpp/dataflow/internal/tainttracking2/TaintTrackingImpl.qll", "cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/tainttracking1/TaintTrackingImpl.qll", diff --git a/cpp/ql/lib/change-notes/2023-03-02-dataflow-conf-module.md b/cpp/ql/lib/change-notes/2023-03-02-dataflow-conf-module.md new file mode 100644 index 00000000000..89190af399f --- /dev/null +++ b/cpp/ql/lib/change-notes/2023-03-02-dataflow-conf-module.md @@ -0,0 +1,9 @@ +--- +category: majorAnalysis +--- +* The main data flow and taint tracking APIs have been changed. The old APIs + remain in place for now and translate to the new through a + backwards-compatible wrapper. If multiple configurations are in scope + simultaneously, then this may affect results slightly. The new API is quite + similar to the old, but makes use of a configuration module instead of a + configuration class. diff --git a/cpp/ql/lib/semmle/code/cpp/Declaration.qll b/cpp/ql/lib/semmle/code/cpp/Declaration.qll index 8def15e8c13..d7cc581c384 100644 --- a/cpp/ql/lib/semmle/code/cpp/Declaration.qll +++ b/cpp/ql/lib/semmle/code/cpp/Declaration.qll @@ -68,7 +68,9 @@ class Declaration extends Locatable, @declaration { * Holds if this declaration has the fully-qualified name `qualifiedName`. * See `getQualifiedName`. */ - predicate hasQualifiedName(string qualifiedName) { this.getQualifiedName() = qualifiedName } + deprecated predicate hasQualifiedName(string qualifiedName) { + this.getQualifiedName() = qualifiedName + } /** * Holds if this declaration has a fully-qualified name with a name-space diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/DataFlow.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/DataFlow.qll index 5af92783e4a..24fdb60eff1 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/DataFlow.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/DataFlow.qll @@ -24,5 +24,6 @@ import cpp * global (inter-procedural) data flow analyses. */ module DataFlow { - import semmle.code.cpp.dataflow.internal.DataFlowImpl + import semmle.code.cpp.dataflow.internal.DataFlow + import semmle.code.cpp.dataflow.internal.DataFlowImpl1 } diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/TaintTracking.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/TaintTracking.qll index b377fe4a33a..57d93068a4c 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/TaintTracking.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/TaintTracking.qll @@ -23,5 +23,6 @@ import semmle.code.cpp.dataflow.DataFlow2 * global (inter-procedural) taint-tracking analyses. */ module TaintTracking { + import semmle.code.cpp.dataflow.internal.tainttracking1.TaintTracking import semmle.code.cpp.dataflow.internal.tainttracking1.TaintTrackingImpl } diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlow.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlow.qll new file mode 100644 index 00000000000..fe9b9b18941 --- /dev/null +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlow.qll @@ -0,0 +1,245 @@ +/** + * Provides an implementation of global (interprocedural) data flow. This file + * re-exports the local (intraprocedural) data flow analysis from + * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed + * through the `Make` and `MakeWithState` modules. + */ + +private import DataFlowImplCommon +private import DataFlowImplSpecific::Private +import DataFlowImplSpecific::Public +import DataFlowImplCommonPublic +private import DataFlowImpl + +/** An input configuration for data flow. */ +signature module ConfigSig { + /** + * Holds if `source` is a relevant data flow source. + */ + predicate isSource(Node source); + + /** + * Holds if `sink` is a relevant data flow sink. + */ + predicate isSink(Node sink); + + /** + * Holds if data flow through `node` is prohibited. This completely removes + * `node` from the data flow graph. + */ + default predicate isBarrier(Node node) { none() } + + /** Holds if data flow into `node` is prohibited. */ + default predicate isBarrierIn(Node node) { none() } + + /** Holds if data flow out of `node` is prohibited. */ + default predicate isBarrierOut(Node node) { none() } + + /** + * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. + */ + default predicate isAdditionalFlowStep(Node node1, Node node2) { none() } + + /** + * Holds if an arbitrary number of implicit read steps of content `c` may be + * taken at `node`. + */ + default predicate allowImplicitRead(Node node, ContentSet c) { none() } + + /** + * Gets the virtual dispatch branching limit when calculating field flow. + * This can be overridden to a smaller value to improve performance (a + * value of 0 disables field flow), or a larger value to get more results. + */ + default int fieldFlowBranchLimit() { result = 2 } + + /** + * Gets a data flow configuration feature to add restrictions to the set of + * valid flow paths. + * + * - `FeatureHasSourceCallContext`: + * Assume that sources have some existing call context to disallow + * conflicting return-flow directly following the source. + * - `FeatureHasSinkCallContext`: + * Assume that sinks have some existing call context to disallow + * conflicting argument-to-parameter flow directly preceding the sink. + * - `FeatureEqualSourceSinkCallContext`: + * Implies both of the above and additionally ensures that the entire flow + * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. + */ + default FlowFeature getAFeature() { none() } + + /** Holds if sources should be grouped in the result of `hasFlowPath`. */ + default predicate sourceGrouping(Node source, string sourceGroup) { none() } + + /** Holds if sinks should be grouped in the result of `hasFlowPath`. */ + default predicate sinkGrouping(Node sink, string sinkGroup) { none() } + + /** + * Holds if hidden nodes should be included in the data flow graph. + * + * This feature should only be used for debugging or when the data flow graph + * is not visualized (as it is in a `path-problem` query). + */ + default predicate includeHiddenNodes() { none() } +} + +/** An input configuration for data flow using flow state. */ +signature module StateConfigSig { + bindingset[this] + class FlowState; + + /** + * Holds if `source` is a relevant data flow source with the given initial + * `state`. + */ + predicate isSource(Node source, FlowState state); + + /** + * Holds if `sink` is a relevant data flow sink accepting `state`. + */ + predicate isSink(Node sink, FlowState state); + + /** + * Holds if data flow through `node` is prohibited. This completely removes + * `node` from the data flow graph. + */ + default predicate isBarrier(Node node) { none() } + + /** + * Holds if data flow through `node` is prohibited when the flow state is + * `state`. + */ + predicate isBarrier(Node node, FlowState state); + + /** Holds if data flow into `node` is prohibited. */ + default predicate isBarrierIn(Node node) { none() } + + /** Holds if data flow out of `node` is prohibited. */ + default predicate isBarrierOut(Node node) { none() } + + /** + * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. + */ + default predicate isAdditionalFlowStep(Node node1, Node node2) { none() } + + /** + * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. + * This step is only applicable in `state1` and updates the flow state to `state2`. + */ + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2); + + /** + * Holds if an arbitrary number of implicit read steps of content `c` may be + * taken at `node`. + */ + default predicate allowImplicitRead(Node node, ContentSet c) { none() } + + /** + * Gets the virtual dispatch branching limit when calculating field flow. + * This can be overridden to a smaller value to improve performance (a + * value of 0 disables field flow), or a larger value to get more results. + */ + default int fieldFlowBranchLimit() { result = 2 } + + /** + * Gets a data flow configuration feature to add restrictions to the set of + * valid flow paths. + * + * - `FeatureHasSourceCallContext`: + * Assume that sources have some existing call context to disallow + * conflicting return-flow directly following the source. + * - `FeatureHasSinkCallContext`: + * Assume that sinks have some existing call context to disallow + * conflicting argument-to-parameter flow directly preceding the sink. + * - `FeatureEqualSourceSinkCallContext`: + * Implies both of the above and additionally ensures that the entire flow + * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. + */ + default FlowFeature getAFeature() { none() } + + /** Holds if sources should be grouped in the result of `hasFlowPath`. */ + default predicate sourceGrouping(Node source, string sourceGroup) { none() } + + /** Holds if sinks should be grouped in the result of `hasFlowPath`. */ + default predicate sinkGrouping(Node sink, string sinkGroup) { none() } + + /** + * Holds if hidden nodes should be included in the data flow graph. + * + * This feature should only be used for debugging or when the data flow graph + * is not visualized (as it is in a `path-problem` query). + */ + default predicate includeHiddenNodes() { none() } +} + +/** + * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` + * measured in approximate number of interprocedural steps. + */ +signature int explorationLimitSig(); + +/** + * The output of a data flow computation. + */ +signature module DataFlowSig { + /** + * A `Node` augmented with a call context (except for sinks) and an access path. + * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. + */ + class PathNode; + + /** + * Holds if data can flow from `source` to `sink`. + * + * The corresponding paths are generated from the end-points and the graph + * included in the module `PathGraph`. + */ + predicate hasFlowPath(PathNode source, PathNode sink); + + /** + * Holds if data can flow from `source` to `sink`. + */ + predicate hasFlow(Node source, Node sink); + + /** + * Holds if data can flow from some source to `sink`. + */ + predicate hasFlowTo(Node sink); + + /** + * Holds if data can flow from some source to `sink`. + */ + predicate hasFlowToExpr(DataFlowExpr sink); +} + +/** + * Constructs a standard data flow computation. + */ +module Make implements DataFlowSig { + private module C implements FullStateConfigSig { + import DefaultState + import Config + } + + import Impl +} + +/** + * Constructs a data flow computation using flow state. + */ +module MakeWithState implements DataFlowSig { + private module C implements FullStateConfigSig { + import Config + } + + import Impl +} diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll index 1b969756b09..0afcba55582 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll @@ -1,135 +1,75 @@ /** - * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from - * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed - * through the `Configuration` class. This file exists in several identical - * copies, allowing queries to use multiple `Configuration` classes that depend - * on each other without introducing mutual recursion among those configurations. + * INTERNAL: Do not use. + * + * Provides an implementation of global (interprocedural) data flow. */ private import DataFlowImplCommon private import DataFlowImplSpecific::Private -import DataFlowImplSpecific::Public -import DataFlowImplCommonPublic +private import DataFlowImplSpecific::Public +private import DataFlowImplCommonPublic +import DataFlow /** - * A configuration of interprocedural data flow analysis. This defines - * sources, sinks, and any other configurable aspect of the analysis. Each - * use of the global data flow library must define its own unique extension - * of this abstract class. To create a configuration, extend this class with - * a subclass whose characteristic predicate is a unique singleton string. - * For example, write - * - * ```ql - * class MyAnalysisConfiguration extends DataFlow::Configuration { - * MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" } - * // Override `isSource` and `isSink`. - * // Optionally override `isBarrier`. - * // Optionally override `isAdditionalFlowStep`. - * } - * ``` - * Conceptually, this defines a graph where the nodes are `DataFlow::Node`s and - * the edges are those data-flow steps that preserve the value of the node - * along with any additional edges defined by `isAdditionalFlowStep`. - * Specifying nodes in `isBarrier` will remove those nodes from the graph, and - * specifying nodes in `isBarrierIn` and/or `isBarrierOut` will remove in-going - * and/or out-going edges from those nodes, respectively. - * - * Then, to query whether there is flow between some `source` and `sink`, - * write - * - * ```ql - * exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink)) - * ``` - * - * Multiple configurations can coexist, but two classes extending - * `DataFlow::Configuration` should never depend on each other. One of them - * should instead depend on a `DataFlow2::Configuration`, a - * `DataFlow3::Configuration`, or a `DataFlow4::Configuration`. + * An input configuration for data flow using flow state. This signature equals + * `StateConfigSig`, but requires explicit implementation of all predicates. */ -abstract class Configuration extends string { +signature module FullStateConfigSig { bindingset[this] - Configuration() { any() } - - /** - * Holds if `source` is a relevant data flow source. - */ - predicate isSource(Node source) { none() } + class FlowState; /** * Holds if `source` is a relevant data flow source with the given initial * `state`. */ - predicate isSource(Node source, FlowState state) { none() } - - /** - * Holds if `sink` is a relevant data flow sink. - */ - predicate isSink(Node sink) { none() } + predicate isSource(Node source, FlowState state); /** * Holds if `sink` is a relevant data flow sink accepting `state`. */ - predicate isSink(Node sink, FlowState state) { none() } + predicate isSink(Node sink, FlowState state); /** * Holds if data flow through `node` is prohibited. This completely removes * `node` from the data flow graph. */ - predicate isBarrier(Node node) { none() } + predicate isBarrier(Node node); /** * Holds if data flow through `node` is prohibited when the flow state is * `state`. */ - predicate isBarrier(Node node, FlowState state) { none() } + predicate isBarrier(Node node, FlowState state); /** Holds if data flow into `node` is prohibited. */ - predicate isBarrierIn(Node node) { none() } + predicate isBarrierIn(Node node); /** Holds if data flow out of `node` is prohibited. */ - predicate isBarrierOut(Node node) { none() } - - /** - * DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead. - * - * Holds if data flow through nodes guarded by `guard` is prohibited. - */ - deprecated predicate isBarrierGuard(BarrierGuard guard) { none() } - - /** - * DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead. - * - * Holds if data flow through nodes guarded by `guard` is prohibited when - * the flow state is `state` - */ - deprecated predicate isBarrierGuard(BarrierGuard guard, FlowState state) { none() } + predicate isBarrierOut(Node node); /** * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. */ - predicate isAdditionalFlowStep(Node node1, Node node2) { none() } + predicate isAdditionalFlowStep(Node node1, Node node2); /** * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. * This step is only applicable in `state1` and updates the flow state to `state2`. */ - predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { - none() - } + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2); /** * Holds if an arbitrary number of implicit read steps of content `c` may be * taken at `node`. */ - predicate allowImplicitRead(Node node, ContentSet c) { none() } + predicate allowImplicitRead(Node node, ContentSet c); /** * Gets the virtual dispatch branching limit when calculating field flow. * This can be overridden to a smaller value to improve performance (a * value of 0 disables field flow), or a larger value to get more results. */ - int fieldFlowBranchLimit() { result = 2 } + int fieldFlowBranchLimit(); /** * Gets a data flow configuration feature to add restrictions to the set of @@ -144,1720 +84,680 @@ abstract class Configuration extends string { * - `FeatureEqualSourceSinkCallContext`: * Implies both of the above and additionally ensures that the entire flow * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. */ - FlowFeature getAFeature() { none() } + FlowFeature getAFeature(); /** Holds if sources should be grouped in the result of `hasFlowPath`. */ - predicate sourceGrouping(Node source, string sourceGroup) { none() } + predicate sourceGrouping(Node source, string sourceGroup); /** Holds if sinks should be grouped in the result of `hasFlowPath`. */ - predicate sinkGrouping(Node sink, string sinkGroup) { none() } - - /** - * Holds if data may flow from `source` to `sink` for this configuration. - */ - predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } - - /** - * Holds if data may flow from `source` to `sink` for this configuration. - * - * The corresponding paths are generated from the end-points and the graph - * included in the module `PathGraph`. - */ - predicate hasFlowPath(PathNode source, PathNode sink) { hasFlowPath(source, sink, this) } - - /** - * Holds if data may flow from some source to `sink` for this configuration. - */ - predicate hasFlowTo(Node sink) { - sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode() - } - - /** - * Holds if data may flow from some source to `sink` for this configuration. - */ - predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } - - /** - * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` - * measured in approximate number of interprocedural steps. - */ - int explorationLimit() { none() } + predicate sinkGrouping(Node sink, string sinkGroup); /** * Holds if hidden nodes should be included in the data flow graph. * * This feature should only be used for debugging or when the data flow graph - * is not visualized (for example in a `path-problem` query). + * is not visualized (as it is in a `path-problem` query). */ - predicate includeHiddenNodes() { none() } + predicate includeHiddenNodes(); +} - /** - * Holds if there is a partial data flow path from `source` to `node`. The - * approximate distance between `node` and the closest source is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards sink definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sources is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - */ - final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { - partialFlow(source, node, this) and - dist = node.getSourceDistance() - } +/** + * Provides default `FlowState` implementations given a `StateConfigSig`. + */ +module DefaultState { + class FlowState = Unit; - /** - * Holds if there is a partial data flow path from `node` to `sink`. The - * approximate distance between `node` and the closest sink is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards source definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sinks is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - * - * Note that reverse flow has slightly lower precision than the corresponding - * forward flow, as reverse flow disregards type pruning among other features. - */ - final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { - revPartialFlow(node, sink, this) and - dist = node.getSinkDistance() + predicate isSource(Node source, FlowState state) { Config::isSource(source) and exists(state) } + + predicate isSink(Node sink, FlowState state) { Config::isSink(sink) and exists(state) } + + predicate isBarrier(Node node, FlowState state) { none() } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + none() } } /** - * This class exists to prevent mutual recursion between the user-overridden - * member predicates of `Configuration` and the rest of the data-flow library. - * Good performance cannot be guaranteed in the presence of such recursion, so - * it should be replaced by using more than one copy of the data flow library. + * Constructs a data flow computation given a full input configuration. */ -abstract private class ConfigurationRecursionPrevention extends Configuration { - bindingset[this] - ConfigurationRecursionPrevention() { any() } +module Impl { + private class FlowState = Config::FlowState; - override predicate hasFlow(Node source, Node sink) { - strictcount(Node n | this.isSource(n)) < 0 - or - strictcount(Node n | this.isSource(n, _)) < 0 - or - strictcount(Node n | this.isSink(n)) < 0 - or - strictcount(Node n | this.isSink(n, _)) < 0 - or - strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0 - or - strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, _, n2, _)) < 0 - or - super.hasFlow(source, sink) - } -} + private newtype TNodeEx = + TNodeNormal(Node n) or + TNodeImplicitRead(Node n, boolean hasRead) { + Config::allowImplicitRead(n, _) and hasRead = [false, true] + } -private newtype TNodeEx = - TNodeNormal(Node n) or - TNodeImplicitRead(Node n, boolean hasRead) { - any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true] - } - -private class NodeEx extends TNodeEx { - string toString() { - result = this.asNode().toString() - or - exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") - } - - Node asNode() { this = TNodeNormal(result) } - - predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } - - Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } - - pragma[nomagic] - private DataFlowCallable getEnclosingCallable0() { - nodeEnclosingCallable(this.projectToNode(), result) - } - - pragma[inline] - DataFlowCallable getEnclosingCallable() { - pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) - } - - pragma[nomagic] - private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } - - pragma[inline] - DataFlowType getDataFlowType() { - pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) - } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -private class ArgNodeEx extends NodeEx { - ArgNodeEx() { this.asNode() instanceof ArgNode } -} - -private class ParamNodeEx extends NodeEx { - ParamNodeEx() { this.asNode() instanceof ParamNode } - - predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { - this.asNode().(ParamNode).isParameterOf(c, pos) - } - - ParameterPosition getPosition() { this.isParameterOf(_, result) } -} - -private class RetNodeEx extends NodeEx { - RetNodeEx() { this.asNode() instanceof ReturnNodeExt } - - ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } - - ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } -} - -private predicate inBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierIn(n) - | - config.isSource(n) or config.isSource(n, _) - ) -} - -private predicate outBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierOut(n) - | - config.isSink(n) or config.isSink(n, _) - ) -} - -/** A bridge class to access the deprecated `isBarrierGuard`. */ -private class BarrierGuardGuardedNodeBridge extends Unit { - abstract predicate guardedNode(Node n, Configuration config); - - abstract predicate guardedNode(Node n, FlowState state, Configuration config); -} - -private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { - deprecated override predicate guardedNode(Node n, Configuration config) { - exists(BarrierGuard g | - config.isBarrierGuard(g) and - n = g.getAGuardedNode() - ) - } - - deprecated override predicate guardedNode(Node n, FlowState state, Configuration config) { - exists(BarrierGuard g | - config.isBarrierGuard(g, state) and - n = g.getAGuardedNode() - ) - } -} - -pragma[nomagic] -private predicate fullBarrier(NodeEx node, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n) - or - config.isBarrierIn(n) and - not config.isSource(n) and - not config.isSource(n, _) - or - config.isBarrierOut(n) and - not config.isSink(n) and - not config.isSink(n, _) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config) - ) -} - -pragma[nomagic] -private predicate stateBarrier(NodeEx node, FlowState state, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n, state) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config) - ) -} - -pragma[nomagic] -private predicate sourceNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSource(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSource(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -pragma[nomagic] -private predicate sinkNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSink(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSink(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -/** Provides the relevant barriers for a step from `node1` to `node2`. */ -pragma[inline] -private predicate stepFilter(NodeEx node1, NodeEx node2, Configuration config) { - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) -} - -/** - * Holds if data can flow in one local step from `node1` to `node2`. - */ -private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.asNode() = n and - node2.isImplicitReadNode(n, false) and - not fullBarrier(node1, config) - ) -} - -/** - * Holds if the additional step from `node1` to `node2` does not jump between callables. - */ -private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.isImplicitReadNode(n, true) and - node2.asNode() = n and - not fullBarrier(node2, config) - ) -} - -private predicate additionalLocalStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) - ) -} - -/** - * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. - */ -private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if the additional step from `node1` to `node2` jumps between callables. - */ -private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate additionalJumpStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -pragma[nomagic] -private predicate readSet(NodeEx node1, ContentSet c, NodeEx node2, Configuration config) { - readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and - stepFilter(node1, node2, config) - or - exists(Node n | - node2.isImplicitReadNode(n, true) and - node1.isImplicitReadNode(n, _) and - config.allowImplicitRead(n, c) - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(ContentSet cs | - readSet(node1, cs, node2, config) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate clearsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - clearsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate expectsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - expectsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -pragma[nomagic] -private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } - -pragma[nomagic] -private predicate hasReadStep(Content c, Configuration config) { read(_, c, _, config) } - -pragma[nomagic] -private predicate store( - NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config -) { - store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), - contentType) and - hasReadStep(tc.getContent(), config) and - stepFilter(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { - viableReturnPosOut(call, pos, out.asNode()) -} - -pragma[nomagic] -private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { - viableParamArg(call, p.asNode(), arg.asNode()) -} - -/** - * Holds if field flow should be used for the given configuration. - */ -private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } - -private predicate hasSourceCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSourceCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate hasSinkCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSinkCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if flow from `p` to a return node of kind `kind` is allowed. - * - * We don't expect a parameter to return stored in itself, unless - * explicitly allowed - */ -bindingset[p, kind] -private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { - exists(ParameterPosition pos | p.isParameterOf(_, pos) | - not kind.(ParamUpdateReturnKind).getPosition() = pos - or - allowParameterReturnInSelfCached(p.asNode()) - ) -} - -private module Stage1 implements StageSig { - class Ap extends int { - // workaround for bad functionality-induced joins (happens when using `Unit`) - pragma[nomagic] - Ap() { this in [0 .. 1] and this < 1 } - } - - private class Cc = boolean; - - /* Begin: Stage 1 logic. */ - /** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `cc` records whether the node is reached through an - * argument in a call. - */ - private predicate fwdFlow(NodeEx node, Cc cc, Configuration config) { - sourceNode(node, _, config) and - if hasSourceCallCtx(config) then cc = true else cc = false - or - exists(NodeEx mid | fwdFlow(mid, cc, config) | - localFlowStep(mid, node, config) or - additionalLocalFlowStep(mid, node, config) or - additionalLocalStateStep(mid, _, node, _, config) - ) - or - exists(NodeEx mid | fwdFlow(mid, _, config) and cc = false | - jumpStep(mid, node, config) or - additionalJumpStep(mid, node, config) or - additionalJumpStateStep(mid, _, node, _, config) - ) - or - // store - exists(NodeEx mid | - useFieldFlow(config) and - fwdFlow(mid, cc, config) and - store(mid, _, node, _, config) - ) - or - // read - exists(ContentSet c | - fwdFlowReadSet(c, node, cc, config) and - fwdFlowConsCandSet(c, _, config) - ) - or - // flow into a callable - fwdFlowIn(_, _, _, node, config) and - cc = true - or - // flow out of a callable - fwdFlowOut(_, node, false, config) and - cc = false - or - // flow through a callable - exists(DataFlowCall call | - fwdFlowOutFromArg(call, node, config) and - fwdFlowIsEntered(call, cc, config) - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowIn( - DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p, Configuration config - ) { - // call context cannot help reduce virtual dispatch - fwdFlow(arg, cc, config) and - viableParamArgEx(call, p, arg) and - not fullBarrier(p, config) and - ( - cc = false + private class NodeEx extends TNodeEx { + string toString() { + result = this.asNode().toString() or - cc = true and - not reducedViableImplInCallContext(call, _, _) - ) - or - // call context may help reduce virtual dispatch - exists(DataFlowCallable target | - fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target, config) and - target = viableImplInSomeFwdFlowCallContextExt(call, config) and - cc = true - ) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - fwdFlowIn(call, _, cc, _, config) - } - - pragma[nomagic] - private predicate fwdFlowInReducedViableImplInSomeCallContext( - DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target, Configuration config - ) { - fwdFlow(arg, true, config) and - viableParamArgEx(call, p, arg) and - reducedViableImplInCallContext(call, _, _) and - target = p.getEnclosingCallable() and - not fullBarrier(p, config) - } - - /** - * Gets a viable dispatch target of `call` in the context `ctx`. This is - * restricted to those `call`s for which a context might make a difference, - * and to `ctx`s that are reachable in `fwdFlow`. - */ - pragma[nomagic] - private DataFlowCallable viableImplInSomeFwdFlowCallContextExt( - DataFlowCall call, Configuration config - ) { - exists(DataFlowCall ctx | - fwdFlowIsEntered(ctx, _, config) and - result = viableImplInCallContextExt(call, ctx) - ) - } - - private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) } - - pragma[nomagic] - private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc, Configuration config) { - exists(NodeEx mid | - fwdFlow(mid, cc, config) and - readSet(mid, c, node, config) - ) - } - - /** - * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - fwdFlow(mid, _, config) and - store(mid, tc, node, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `cs` may be interpreted in a read as the target of some store - * into `c`, in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCandSet(ContentSet cs, Content c, Configuration config) { - fwdFlowConsCand(c, config) and - c = cs.getAReadContent() - } - - pragma[nomagic] - private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { - exists(RetNodeEx ret | - fwdFlow(ret, cc, config) and - ret.getReturnPosition() = pos - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) { - exists(ReturnPosition pos | - fwdFlowReturnPosition(pos, cc, config) and - viableReturnPosOutEx(call, pos, out) and - not fullBarrier(out, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) { - fwdFlowOut(call, out, true, config) - } - - private predicate stateStepFwd(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1 | - additionalLocalStateStep(node1, state1, _, state2, config) or - additionalJumpStateStep(node1, state1, _, state2, config) - | - fwdFlow(node1, config) - ) - } - - private predicate fwdFlowState(FlowState state, Configuration config) { - sourceNode(_, state, config) - or - exists(FlowState state0 | - fwdFlowState(state0, config) and - stateStepFwd(state0, state, config) - ) - } - - /** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink. - */ - pragma[nomagic] - private predicate revFlow(NodeEx node, boolean toReturn, Configuration config) { - revFlow0(node, toReturn, config) and - fwdFlow(node, config) - } - - pragma[nomagic] - private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) { - exists(FlowState state | - fwdFlow(node, pragma[only_bind_into](config)) and - sinkNode(node, state, config) and - fwdFlowState(state, pragma[only_bind_into](config)) and - if hasSinkCallCtx(config) then toReturn = true else toReturn = false - ) - or - exists(NodeEx mid | revFlow(mid, toReturn, config) | - localFlowStep(node, mid, config) or - additionalLocalFlowStep(node, mid, config) or - additionalLocalStateStep(node, _, mid, _, config) - ) - or - exists(NodeEx mid | revFlow(mid, _, config) and toReturn = false | - jumpStep(node, mid, config) or - additionalJumpStep(node, mid, config) or - additionalJumpStateStep(node, _, mid, _, config) - ) - or - // store - exists(Content c | - revFlowStore(c, node, toReturn, config) and - revFlowConsCand(c, config) - ) - or - // read - exists(NodeEx mid, ContentSet c | - readSet(node, c, mid, config) and - fwdFlowConsCandSet(c, _, pragma[only_bind_into](config)) and - revFlow(mid, toReturn, pragma[only_bind_into](config)) - ) - or - // flow into a callable - revFlowIn(_, node, false, config) and - toReturn = false - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(pos, config) and - node.(RetNodeEx).getReturnPosition() = pos and - toReturn = true - ) - or - // flow through a callable - exists(DataFlowCall call | - revFlowInToReturn(call, node, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - /** - * Holds if `c` is the target of a read in the flow covered by `revFlow`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, ContentSet cs | - fwdFlow(node, pragma[only_bind_into](config)) and - readSet(node, cs, mid, config) and - fwdFlowConsCandSet(cs, c, pragma[only_bind_into](config)) and - revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) { - exists(NodeEx mid, TypedContent tc | - revFlow(mid, toReturn, pragma[only_bind_into](config)) and - fwdFlowConsCand(c, pragma[only_bind_into](config)) and - store(node, tc, mid, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `c` is the target of both a read and a store in the flow covered - * by `revFlow`. - */ - pragma[nomagic] - additional predicate revFlowIsReadAndStored(Content c, Configuration conf) { - revFlowConsCand(c, conf) and - revFlowStore(c, _, _, conf) - } - - pragma[nomagic] - additional predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config - ) { - fwdFlowReturnPosition(pos, _, config) and - viableReturnPosOutEx(call, pos, out) - } - - pragma[nomagic] - private predicate revFlowOut(ReturnPosition pos, Configuration config) { - exists(NodeEx out | - revFlow(out, _, config) and - viableReturnPosOutNodeCandFwd1(_, pos, out, config) - ) - } - - pragma[nomagic] - additional predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config - ) { - fwdFlowIn(call, arg, _, p, config) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate revFlowIn( - DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config - ) { - exists(ParamNodeEx p | - revFlow(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) { - revFlowIn(call, arg, true, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(NodeEx out | - revFlow(out, toReturn, config) and - fwdFlowOutFromArg(call, out, config) - ) - } - - private predicate stateStepRev(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - additionalLocalStateStep(node1, state1, node2, state2, config) or - additionalJumpStateStep(node1, state1, node2, state2, config) - | - revFlow(node1, _, pragma[only_bind_into](config)) and - revFlow(node2, _, pragma[only_bind_into](config)) and - fwdFlowState(state1, pragma[only_bind_into](config)) and - fwdFlowState(state2, pragma[only_bind_into](config)) - ) - } - - additional predicate revFlowState(FlowState state, Configuration config) { - exists(NodeEx node | - sinkNode(node, state, config) and - revFlow(node, _, pragma[only_bind_into](config)) and - fwdFlowState(state, pragma[only_bind_into](config)) - ) - or - exists(FlowState state0 | - revFlowState(state0, config) and - stateStepRev(state, state0, config) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Content c | - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - revFlow(node2, pragma[only_bind_into](config)) and - store(node1, tc, node2, contentType, config) and - c = tc.getContent() and - exists(ap1) - ) - } - - pragma[nomagic] - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) { - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - read(n1, c, n2, pragma[only_bind_into](config)) and - revFlow(n2, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, config) and - exists(ap) - } - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, _, pragma[only_bind_into](config)) and - exists(state) and - exists(ap) - } - - private predicate throughFlowNodeCand(NodeEx node, Configuration config) { - revFlow(node, true, config) and - fwdFlow(node, true, config) and - not inBarrier(node, config) and - not outBarrier(node, config) - } - - /** Holds if flow may return from `callable`. */ - pragma[nomagic] - private predicate returnFlowCallableNodeCand( - DataFlowCallable callable, ReturnKindExt kind, Configuration config - ) { - exists(RetNodeEx ret | - throughFlowNodeCand(ret, config) and - callable = ret.getEnclosingCallable() and - kind = ret.getKind() - ) - } - - /** - * Holds if flow may enter through `p` and reach a return node making `p` a - * candidate for the origin of a summary. - */ - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(DataFlowCallable c, ReturnKindExt kind | - throughFlowNodeCand(p, config) and - returnFlowCallableNodeCand(c, kind, config) and - p.getEnclosingCallable() = c and - exists(ap) and - parameterFlowThroughAllowed(p, kind) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - throughFlowNodeCand(ret, config) and - kind = ret.getKind() and - exists(argAp) and - exists(ap) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, boolean toReturn | - revFlow(arg, toReturn, config) and - revFlowInToReturn(call, arg, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, config)) and - fields = count(Content f0 | fwdFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | fwdFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, config)) and - fields = count(Content f0 | revFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | revFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | revFlow(n, b, config)) - } - /* End: Stage 1 logic. */ -} - -pragma[noinline] -private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - localFlowStep(node1, node2, config) -} - -pragma[noinline] -private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - additionalLocalFlowStep(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCand1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config -) { - Stage1::revFlow(out, config) and - Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, Configuration config -) { - exists(ReturnPosition pos | - viableReturnPosOutNodeCand1(call, pos, out, config) and - pos = ret.getReturnPosition() and - kind = pos.getKind() and - Stage1::revFlow(ret, config) and - not outBarrier(ret, config) and - not inBarrier(out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCand1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config -) { - Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and - Stage1::revFlow(arg, config) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config -) { - viableParamArgNodeCand1(call, p, arg, config) and - Stage1::revFlow(p, config) and - not outBarrier(arg, config) and - not inBarrier(p, config) -} - -/** - * Gets the amount of forward branching on the origin of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int branch(NodeEx n1, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n1, _, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) - ) -} - -/** - * Gets the amount of backward branching on the target of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int join(NodeEx n2, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n, _, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) - ) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. The - * `allowsFieldFlow` flag indicates whether the branching is within the limit - * specified by the configuration. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, ret, kind, out, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(ret, pragma[only_bind_into](config)) and - j = join(out, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. The `allowsFieldFlow` flag indicates whether - * the branching is within the limit specified by the configuration. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config -) { - flowIntoCallNodeCand1(call, arg, p, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(arg, pragma[only_bind_into](config)) and - j = join(p, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -private signature module StageSig { - class Ap; - - predicate revFlow(NodeEx node, Configuration config); - - predicate revFlowAp(NodeEx node, Ap ap, Configuration config); - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config); - - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config); - - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config); - - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ); - - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ); - - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config); -} - -private module MkStage { - class ApApprox = PrevStage::Ap; - - signature module StageParam { - class Ap; - - class ApNil extends Ap; - - bindingset[result, ap] - ApApprox getApprox(Ap ap); - - ApNil getApNil(NodeEx node); - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail); - - /** - * An approximation of `Content` that corresponds to the precision level of - * `Ap`, such that the mappings from both `Ap` and `Content` to this type - * are functional. - */ - class ApHeadContent; - - ApHeadContent getHeadContent(Ap ap); - - ApHeadContent projectToHeadContent(Content c); - - class ApOption; - - ApOption apNone(); - - ApOption apSome(Ap ap); - - class Cc; - - class CcCall extends Cc; - - // TODO: member predicate on CcCall - predicate matchesCall(CcCall cc, DataFlowCall call); - - class CcNoCall extends Cc; - - Cc ccNone(); - - CcCall ccSomeCall(); - - class LocalCc; - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc); - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ); - - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config - ); - - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config - ); - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config); - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType); - } - - module Stage implements StageSig { - import Param - - /* Begin: Stage logic. */ - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - private predicate revFlowApAlias(NodeEx node, ApApprox apa, Configuration config) { - PrevStage::revFlowAp(node, apa, config) + exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") } - pragma[nomagic] - private predicate flowIntoCallApa( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa, - Configuration config - ) { - flowIntoCall(call, arg, p, allowsFieldFlow, config) and - PrevStage::revFlowAp(p, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(arg, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } + Node asNode() { this = TNodeNormal(result) } + + predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } + + Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } pragma[nomagic] - private predicate flowOutOfCallApa( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - ApApprox apa, Configuration config - ) { - flowOutOfCall(call, ret, kind, out, allowsFieldFlow, config) and - PrevStage::revFlowAp(out, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(ret, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowThroughOutOfCall( - DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, - ApApprox argApa, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa, pragma[only_bind_into](config)) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) and - matchesCall(ccc, call) - ) - } - - /** - * Holds if `node` is reachable with access path `ap` from a source in the - * configuration `config`. - * - * The call context `cc` records whether the node is reached through an - * argument in a call, and if so, `summaryCtx` and `argAp` record the - * corresponding parameter position and access path of that argument, respectively. - */ - pragma[nomagic] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa, config) and - PrevStage::revFlow(node, state, apa, config) and - filter(node, state, ap, config) + private DataFlowCallable getEnclosingCallable0() { + nodeEnclosingCallable(this.projectToNode(), result) } pragma[inline] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, cc, summaryCtx, argAp, ap, _, config) + DataFlowCallable getEnclosingCallable() { + pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) } pragma[nomagic] - private predicate fwdFlow0( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config + private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } + + pragma[inline] + DataFlowType getDataFlowType() { + pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) + } + + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn ) { - sourceNode(node, state, config) and - (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and - argAp = apNone() and - summaryCtx = TParamNodeNone() and - ap = getApNil(node) and - apa = getApprox(ap) + this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + private class ArgNodeEx extends NodeEx { + ArgNodeEx() { this.asNode() instanceof ArgNode } + } + + private class ParamNodeEx extends NodeEx { + ParamNodeEx() { this.asNode() instanceof ParamNode } + + predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { + this.asNode().(ParamNode).isParameterOf(c, pos) + } + + ParameterPosition getPosition() { this.isParameterOf(_, result) } + } + + private class RetNodeEx extends NodeEx { + RetNodeEx() { this.asNode() instanceof ReturnNodeExt } + + ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } + + ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } + } + + private predicate inBarrier(NodeEx node) { + exists(Node n | + node.asNode() = n and + Config::isBarrierIn(n) and + Config::isSource(n, _) + ) + } + + private predicate outBarrier(NodeEx node) { + exists(Node n | + node.asNode() = n and + Config::isBarrierOut(n) and + Config::isSink(n, _) + ) + } + + pragma[nomagic] + private predicate fullBarrier(NodeEx node) { + exists(Node n | node.asNode() = n | + Config::isBarrier(n) or - exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | - fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0, config) and - localCc = getLocalCc(mid, cc) - | - localStep(mid, state0, node, state, true, _, config, localCc) and - ap = ap0 and - apa = apa0 - or - localStep(mid, state0, node, state, false, ap, config, localCc) and - ap0 instanceof ApNil and - apa = getApprox(ap) + Config::isBarrierIn(n) and + not Config::isSource(n, _) + or + Config::isBarrierOut(n) and + not Config::isSink(n, _) + ) + } + + pragma[nomagic] + private predicate stateBarrier(NodeEx node, FlowState state) { + exists(Node n | node.asNode() = n | Config::isBarrier(n, state)) + } + + pragma[nomagic] + private predicate sourceNode(NodeEx node, FlowState state) { + Config::isSource(node.asNode(), state) and + not fullBarrier(node) and + not stateBarrier(node, state) + } + + pragma[nomagic] + private predicate sinkNode(NodeEx node, FlowState state) { + Config::isSink(node.asNode(), state) and + not fullBarrier(node) and + not stateBarrier(node, state) + } + + /** Provides the relevant barriers for a step from `node1` to `node2`. */ + pragma[inline] + private predicate stepFilter(NodeEx node1, NodeEx node2) { + not outBarrier(node1) and + not inBarrier(node2) and + not fullBarrier(node1) and + not fullBarrier(node2) + } + + /** + * Holds if data can flow in one local step from `node1` to `node2`. + */ + private predicate localFlowStepEx(NodeEx node1, NodeEx node2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and + stepFilter(node1, node2) + ) + or + exists(Node n | + Config::allowImplicitRead(n, _) and + node1.asNode() = n and + node2.isImplicitReadNode(n, false) and + not fullBarrier(node1) + ) + } + + /** + * Holds if the additional step from `node1` to `node2` does not jump between callables. + */ + private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + Config::isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and + getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and + stepFilter(node1, node2) + ) + or + exists(Node n | + Config::allowImplicitRead(n, _) and + node1.isImplicitReadNode(n, true) and + node2.asNode() = n and + not fullBarrier(node2) + ) + } + + private predicate additionalLocalStateStep(NodeEx node1, FlowState s1, NodeEx node2, FlowState s2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + Config::isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and + getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and + stepFilter(node1, node2) and + not stateBarrier(node1, s1) and + not stateBarrier(node2, s2) + ) + } + + /** + * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. + */ + private predicate jumpStepEx(NodeEx node1, NodeEx node2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and + stepFilter(node1, node2) and + not Config::getAFeature() instanceof FeatureEqualSourceSinkCallContext + ) + } + + /** + * Holds if the additional step from `node1` to `node2` jumps between callables. + */ + private predicate additionalJumpStep(NodeEx node1, NodeEx node2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + Config::isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and + getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and + stepFilter(node1, node2) and + not Config::getAFeature() instanceof FeatureEqualSourceSinkCallContext + ) + } + + private predicate additionalJumpStateStep(NodeEx node1, FlowState s1, NodeEx node2, FlowState s2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + Config::isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and + getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and + stepFilter(node1, node2) and + not stateBarrier(node1, s1) and + not stateBarrier(node2, s2) and + not Config::getAFeature() instanceof FeatureEqualSourceSinkCallContext + ) + } + + pragma[nomagic] + private predicate readSetEx(NodeEx node1, ContentSet c, NodeEx node2) { + readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and + stepFilter(node1, node2) + or + exists(Node n | + node2.isImplicitReadNode(n, true) and + node1.isImplicitReadNode(n, _) and + Config::allowImplicitRead(n, c) + ) + } + + // inline to reduce fan-out via `getAReadContent` + bindingset[c] + private predicate read(NodeEx node1, Content c, NodeEx node2) { + exists(ContentSet cs | + readSetEx(node1, cs, node2) and + pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() + ) + } + + // inline to reduce fan-out via `getAReadContent` + bindingset[c] + private predicate clearsContentEx(NodeEx n, Content c) { + exists(ContentSet cs | + clearsContentCached(n.asNode(), cs) and + pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() + ) + } + + // inline to reduce fan-out via `getAReadContent` + bindingset[c] + private predicate expectsContentEx(NodeEx n, Content c) { + exists(ContentSet cs | + expectsContentCached(n.asNode(), cs) and + pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() + ) + } + + pragma[nomagic] + private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } + + pragma[nomagic] + private predicate hasReadStep(Content c) { read(_, c, _) } + + pragma[nomagic] + private predicate storeEx(NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType) { + store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), + contentType) and + hasReadStep(tc.getContent()) and + stepFilter(node1, node2) + } + + pragma[nomagic] + private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { + viableReturnPosOut(call, pos, out.asNode()) + } + + pragma[nomagic] + private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { + viableParamArg(call, p.asNode(), arg.asNode()) + } + + /** + * Holds if field flow should be used for the given configuration. + */ + private predicate useFieldFlow() { Config::fieldFlowBranchLimit() >= 1 } + + private predicate hasSourceCallCtx() { + exists(FlowFeature feature | feature = Config::getAFeature() | + feature instanceof FeatureHasSourceCallContext or + feature instanceof FeatureEqualSourceSinkCallContext + ) + } + + private predicate hasSinkCallCtx() { + exists(FlowFeature feature | feature = Config::getAFeature() | + feature instanceof FeatureHasSinkCallContext or + feature instanceof FeatureEqualSourceSinkCallContext + ) + } + + /** + * Holds if flow from `p` to a return node of kind `kind` is allowed. + * + * We don't expect a parameter to return stored in itself, unless + * explicitly allowed + */ + bindingset[p, kind] + private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { + exists(ParameterPosition pos | p.isParameterOf(_, pos) | + not kind.(ParamUpdateReturnKind).getPosition() = pos + or + allowParameterReturnInSelfCached(p.asNode()) + ) + } + + private module Stage1 implements StageSig { + class Ap extends int { + // workaround for bad functionality-induced joins (happens when using `Unit`) + pragma[nomagic] + Ap() { this in [0 .. 1] and this < 1 } + } + + private class Cc = boolean; + + /* Begin: Stage 1 logic. */ + /** + * Holds if `node` is reachable from a source. + * + * The Boolean `cc` records whether the node is reached through an + * argument in a call. + */ + private predicate fwdFlow(NodeEx node, Cc cc) { + sourceNode(node, _) and + if hasSourceCallCtx() then cc = true else cc = false + or + exists(NodeEx mid | fwdFlow(mid, cc) | + localFlowStepEx(mid, node) or + additionalLocalFlowStep(mid, node) or + additionalLocalStateStep(mid, _, node, _) ) or - exists(NodeEx mid | - fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa, pragma[only_bind_into](config)) and - jumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(mid, state, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(mid, state0, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStateStep(mid, state0, node, state, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) + exists(NodeEx mid | fwdFlow(mid, _) and cc = false | + jumpStepEx(mid, node) or + additionalJumpStep(mid, node) or + additionalJumpStateStep(mid, _, node, _) ) or // store - exists(TypedContent tc, Ap ap0 | - fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp, config) and - ap = apCons(tc, ap0) and - apa = getApprox(ap) + exists(NodeEx mid | + useFieldFlow() and + fwdFlow(mid, cc) and + storeEx(mid, _, node, _) ) or // read - exists(Ap ap0, Content c | - fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp, config) and - fwdFlowConsCand(ap0, c, ap, config) and - apa = getApprox(ap) + exists(ContentSet c | + fwdFlowReadSet(c, node, cc) and + fwdFlowConsCandSet(c, _) ) or // flow into a callable - fwdFlowIn(_, node, state, _, cc, _, _, ap, apa, config) and - if PrevStage::parameterMayFlowThrough(node, apa, config) - then ( - summaryCtx = TParamNodeSome(node.asNode()) and - argAp = apSome(ap) - ) else ( - summaryCtx = TParamNodeNone() and argAp = apNone() - ) + fwdFlowIn(_, _, _, node) and + cc = true or // flow out of a callable - exists( - DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, - DataFlowCallable inner - | - fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa, config) and - flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa, config) and - inner = ret.getEnclosingCallable() and - cc = getCallContextReturn(inner, call, innercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) + fwdFlowOut(_, node, false) and + cc = false or // flow through a callable - exists( - DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, ApApprox innerArgApa - | - fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowStore( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(DataFlowType contentType, ApApprox apa1 | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1, config) and - PrevStage::storeStepCand(node1, apa1, tc, node2, contentType, config) and - typecheckStore(ap1, contentType) - ) - } - - /** - * Holds if forward flow with access path `tail` reaches a store of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(TypedContent tc | - fwdFlowStore(_, tail, tc, _, _, _, _, _, config) and - tc.getContent() = c and - cons = apCons(tc, tail) - ) - } - - pragma[nomagic] - private predicate readStepCand( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - PrevStage::readStepCand(node1, c, node2, config) and - apc = projectToHeadContent(c) - } - - bindingset[node1, apc] - pragma[inline_late] - private predicate readStepCand0( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - readStepCand(node1, apc, c, node2, config) - } - - pragma[nomagic] - private predicate fwdFlowRead( - Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(ApHeadContent apc | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap, config) and - apc = getHeadContent(ap) and - readStepCand0(node1, apc, c, node2, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowIn( - DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, - ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa, Configuration config - ) { - exists(ArgNodeEx arg, boolean allowsFieldFlow | - fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa, config) and - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowRetFromArg( - RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, ApApprox argApa, - Ap ap, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - fwdFlow(pragma[only_bind_into](ret), state, ccc, - TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), - pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa), - pragma[only_bind_into](config)) and - kind = ret.getKind() and - parameterFlowThroughAllowed(summaryCtx, kind) and - argApa = getApprox(argAp) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) + exists(DataFlowCall call | + fwdFlowOutFromArg(call, node) and + fwdFlowIsEntered(call, cc) ) } + // inline to reduce the number of iterations pragma[inline] - private predicate fwdFlowThrough0( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, - Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa, config) and - fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp, config) - } - - pragma[nomagic] - private predicate fwdFlowThrough( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa, - config) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` - * and data might flow through the target callable and back out at `call`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered( - DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, - ParamNodeEx p, Ap ap, Configuration config - ) { - exists(ApApprox apa | - fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(p, apa, config) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate storeStepFwd( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config - ) { - fwdFlowStore(node1, ap1, tc, node2, _, _, _, _, config) and - ap2 = apCons(tc, ap1) and - fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _, config) - } - - private predicate readStepFwd( - NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config - ) { - fwdFlowRead(ap1, c, n1, n2, _, _, _, _, config) and - fwdFlowConsCand(ap1, c, ap2, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough0( - DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, - ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, - innerArgApa, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough( - RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, - Ap ap, Configuration config - ) { - exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | - returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa, config) and - pos = ret.getReturnPosition() and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowThroughIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap, - Configuration config - ) { - exists(ApApprox argApa | - flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), - allowsFieldFlow, argApa, pragma[only_bind_into](config)) and - fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa, - pragma[only_bind_into](config)) and - returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap, - pragma[only_bind_into](config)) and - if allowsFieldFlow = false then argAp instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowIntoCallAp( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap, - Configuration config - ) { - exists(ApApprox apa | - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - fwdFlow(arg, _, _, _, _, ap, apa, config) - ) - } - - pragma[nomagic] - private predicate flowOutOfCallAp( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, - Ap ap, Configuration config - ) { - exists(ApApprox apa | - flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa, config) and - fwdFlow(ret, _, _, _, _, ap, apa, config) and - pos = ret.getReturnPosition() - ) - } - - /** - * Holds if `node` with access path `ap` is part of a path from a source to a - * sink in the configuration `config`. - * - * The parameter `returnCtx` records whether (and how) the node must be returned - * from the enclosing callable in order to reach a sink, and if so, `returnAp` - * records the access path of the returned value. - */ - pragma[nomagic] - additional predicate revFlow( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - revFlow0(node, state, returnCtx, returnAp, ap, config) and - fwdFlow(node, state, _, _, _, ap, config) - } - - pragma[nomagic] - private predicate revFlow0( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, _, _, _, ap, config) and - sinkNode(node, state, config) and + private predicate fwdFlowIn(DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p) { + // call context cannot help reduce virtual dispatch + fwdFlow(arg, cc) and + viableParamArgEx(call, p, arg) and + not fullBarrier(p) and ( - if hasSinkCallCtx(config) - then returnCtx = TReturnCtxNoFlowThrough() - else returnCtx = TReturnCtxNone() - ) and - returnAp = apNone() and - ap instanceof ApNil - or - exists(NodeEx mid, FlowState state0 | - localStep(node, state, mid, state0, true, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, ap, config) + cc = false + or + cc = true and + not reducedViableImplInCallContext(call, _, _) ) or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap, pragma[only_bind_into](config)) and - localStep(node, pragma[only_bind_into](state), mid, state0, false, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, nil, pragma[only_bind_into](config)) and - ap instanceof ApNil + // call context may help reduce virtual dispatch + exists(DataFlowCallable target | + fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target) and + target = viableImplInSomeFwdFlowCallContextExt(call) and + cc = true ) - or + } + + /** + * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. + */ + pragma[nomagic] + private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc) { fwdFlowIn(call, _, cc, _) } + + pragma[nomagic] + private predicate fwdFlowInReducedViableImplInSomeCallContext( + DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target + ) { + fwdFlow(arg, true) and + viableParamArgEx(call, p, arg) and + reducedViableImplInCallContext(call, _, _) and + target = p.getEnclosingCallable() and + not fullBarrier(p) + } + + /** + * Gets a viable dispatch target of `call` in the context `ctx`. This is + * restricted to those `call`s for which a context might make a difference, + * and to `ctx`s that are reachable in `fwdFlow`. + */ + pragma[nomagic] + private DataFlowCallable viableImplInSomeFwdFlowCallContextExt(DataFlowCall call) { + exists(DataFlowCall ctx | + fwdFlowIsEntered(ctx, _) and + result = viableImplInCallContextExt(call, ctx) + ) + } + + private predicate fwdFlow(NodeEx node) { fwdFlow(node, _) } + + pragma[nomagic] + private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc) { exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, state, _, _, ap, config) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() + fwdFlow(mid, cc) and + readSetEx(mid, c, node) + ) + } + + /** + * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. + */ + pragma[assume_small_delta] + pragma[nomagic] + private predicate fwdFlowConsCand(Content c) { + exists(NodeEx mid, NodeEx node, TypedContent tc | + not fullBarrier(node) and + useFieldFlow() and + fwdFlow(mid, _) and + storeEx(mid, tc, node, _) and + c = tc.getContent() + ) + } + + /** + * Holds if `cs` may be interpreted in a read as the target of some store + * into `c`, in the flow covered by `fwdFlow`. + */ + pragma[nomagic] + private predicate fwdFlowConsCandSet(ContentSet cs, Content c) { + fwdFlowConsCand(c) and + c = cs.getAReadContent() + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc) { + exists(RetNodeEx ret | + fwdFlow(ret, cc) and + ret.getReturnPosition() = pos + ) + } + + // inline to reduce the number of iterations + pragma[inline] + private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc) and + viableReturnPosOutEx(call, pos, out) and + not fullBarrier(out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out) { + fwdFlowOut(call, out, true) + } + + private predicate stateStepFwd(FlowState state1, FlowState state2) { + exists(NodeEx node1 | + additionalLocalStateStep(node1, state1, _, state2) or + additionalJumpStateStep(node1, state1, _, state2) + | + fwdFlow(node1) + ) + } + + private predicate fwdFlowState(FlowState state) { + sourceNode(_, state) + or + exists(FlowState state0 | + fwdFlowState(state0) and + stateStepFwd(state0, state) + ) + } + + /** + * Holds if `node` is part of a path from a source to a sink. + * + * The Boolean `toReturn` records whether the node must be returned from + * the enclosing callable in order to reach a sink. + */ + pragma[nomagic] + private predicate revFlow(NodeEx node, boolean toReturn) { + revFlow0(node, toReturn) and + fwdFlow(node) + } + + pragma[nomagic] + private predicate revFlow0(NodeEx node, boolean toReturn) { + exists(FlowState state | + fwdFlow(node) and + sinkNode(node, state) and + fwdFlowState(state) and + if hasSinkCallCtx() then toReturn = true else toReturn = false ) or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStep(node, mid, config) and - revFlow(pragma[only_bind_into](mid), state, _, _, nil, pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil + exists(NodeEx mid | revFlow(mid, toReturn) | + localFlowStepEx(node, mid) or + additionalLocalFlowStep(node, mid) or + additionalLocalStateStep(node, _, mid, _) ) or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStateStep(node, state, mid, state0, config) and - revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil, - pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil + exists(NodeEx mid | revFlow(mid, _) and toReturn = false | + jumpStepEx(node, mid) or + additionalJumpStep(node, mid) or + additionalJumpStateStep(node, _, mid, _) ) or // store - exists(Ap ap0, Content c | - revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp, config) and - revFlowConsCand(ap0, c, ap, config) + exists(Content c | + revFlowStore(c, node, toReturn) and + revFlowConsCand(c) ) or // read - exists(NodeEx mid, Ap ap0 | - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - readStepFwd(node, ap, _, mid, ap0, config) + exists(NodeEx mid, ContentSet c | + readSetEx(node, c, mid) and + fwdFlowConsCandSet(c, _) and + revFlow(mid, toReturn) ) or // flow into a callable - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, state, TReturnCtxNone(), returnAp, ap, config) and - flowIntoCallAp(_, node, p, allowsFieldFlow, ap, config) and - (if allowsFieldFlow = false then ap instanceof ApNil else any()) and - returnCtx = TReturnCtxNone() - ) - or - // flow through a callable - exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, node, p, _, ap, innerReturnAp, config) - ) + revFlowIn(_, node, false) and + toReturn = false or // flow out of a callable exists(ReturnPosition pos | - revFlowOut(_, node, pos, state, _, _, ap, config) and - if returnFlowsThrough(node, pos, state, _, _, _, ap, config) - then ( - returnCtx = TReturnCtxMaybeFlowThrough(pos) and - returnAp = apSome(ap) - ) else ( - returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() - ) + revFlowOut(pos) and + node.(RetNodeEx).getReturnPosition() = pos and + toReturn = true + ) + or + // flow through a callable + exists(DataFlowCall call | + revFlowInToReturn(call, node) and + revFlowIsReturned(call, toReturn) ) - } - - pragma[nomagic] - private predicate revFlowStore( - Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, - ReturnCtx returnCtx, ApOption returnAp, Configuration config - ) { - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - storeStepFwd(node, ap, tc, mid, ap0, config) and - tc.getContent() = c } /** - * Holds if reverse flow with access path `tail` reaches a read of `c` - * resulting in access path `cons`. + * Holds if `c` is the target of a read in the flow covered by `revFlow`. */ pragma[nomagic] - private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(NodeEx mid, Ap tail0 | - revFlow(mid, _, _, _, tail, config) and - tail = pragma[only_bind_into](tail0) and - readStepFwd(_, cons, c, mid, tail0, config) + private predicate revFlowConsCand(Content c) { + exists(NodeEx mid, NodeEx node, ContentSet cs | + fwdFlow(node) and + readSetEx(node, cs, mid) and + fwdFlowConsCandSet(cs, c) and + revFlow(pragma[only_bind_into](mid), _) ) } pragma[nomagic] - private predicate revFlowOut( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, - ApOption returnAp, Ap ap, Configuration config + private predicate revFlowStore(Content c, NodeEx node, boolean toReturn) { + exists(NodeEx mid, TypedContent tc | + revFlow(mid, toReturn) and + fwdFlowConsCand(c) and + storeEx(node, tc, mid, _) and + c = tc.getContent() + ) + } + + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + pragma[nomagic] + additional predicate revFlowIsReadAndStored(Content c) { + revFlowConsCand(c) and + revFlowStore(c, _, _) + } + + pragma[nomagic] + additional predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, NodeEx out ) { - exists(NodeEx out, boolean allowsFieldFlow | - revFlow(out, state, returnCtx, returnAp, ap, config) and - flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() + fwdFlowReturnPosition(pos, _) and + viableReturnPosOutEx(call, pos, out) + } + + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos) { + exists(NodeEx out | + revFlow(out, _) and + viableReturnPosOutNodeCandFwd1(_, pos, out) ) } pragma[nomagic] - private predicate revFlowParamToReturn( - ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap, Configuration config - ) { - revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), - pragma[only_bind_into](ap), pragma[only_bind_into](config)) and - parameterFlowThroughAllowed(p, pos.getKind()) and - PrevStage::parameterMayFlowThrough(p, getApprox(ap), config) + additional predicate viableParamArgNodeCandFwd1(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { + fwdFlowIn(call, arg, _, p) + } + + // inline to reduce the number of iterations + pragma[inline] + private predicate revFlowIn(DataFlowCall call, ArgNodeEx arg, boolean toReturn) { + exists(ParamNodeEx p | + revFlow(p, toReturn) and + viableParamArgNodeCandFwd1(call, p, arg) + ) } pragma[nomagic] - private predicate revFlowThrough( - DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, - ApOption returnAp, Ap ap, Ap innerReturnAp, Configuration config - ) { - revFlowParamToReturn(p, state, pos, innerReturnAp, ap, config) and - revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp, config) + private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg) { + revFlowIn(call, arg, true) } /** @@ -1866,896 +766,1704 @@ private module MkStage { * reaching an argument of `call`. */ pragma[nomagic] - private predicate revFlowIsReturned( - DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap, - Configuration config - ) { - exists(RetNodeEx ret, FlowState state, CcCall ccc | - revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap, config) and - returnFlowsThrough(ret, pos, state, ccc, _, _, ap, config) and - matchesCall(ccc, call) + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn) { + exists(NodeEx out | + revFlow(out, toReturn) and + fwdFlowOutFromArg(call, out) + ) + } + + private predicate stateStepRev(FlowState state1, FlowState state2) { + exists(NodeEx node1, NodeEx node2 | + additionalLocalStateStep(node1, state1, node2, state2) or + additionalJumpStateStep(node1, state1, node2, state2) + | + revFlow(node1, _) and + revFlow(node2, _) and + fwdFlowState(state1) and + fwdFlowState(state2) + ) + } + + pragma[nomagic] + additional predicate revFlowState(FlowState state) { + exists(NodeEx node | + sinkNode(node, state) and + revFlow(node, _) and + fwdFlowState(state) + ) + or + exists(FlowState state0 | + revFlowState(state0) and + stateStepRev(state, state0) ) } pragma[nomagic] predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType ) { - exists(Ap ap2, Content c | - PrevStage::storeStepCand(node1, _, tc, node2, contentType, config) and - revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _, config) and - revFlowConsCand(ap2, c, ap1, config) - ) - } - - predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(Ap ap1, Ap ap2 | - revFlow(node2, _, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and - readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _, - pragma[only_bind_into](config)) - ) - } - - additional predicate revFlow(NodeEx node, FlowState state, Configuration config) { - revFlow(node, state, _, _, _, config) - } - - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, _, _, ap, config) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, _, _, _, ap, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, Configuration config) { - revFlow(node, _, _, _, _, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, ap, config) - } - - private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepFwd(_, ap, tc, _, _, config) - } - - private predicate revConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepCand(_, ap, tc, _, _, config) - } - - private predicate validAp(Ap ap, Configuration config) { - revFlow(_, _, _, _, ap, config) and ap instanceof ApNil - or - exists(TypedContent head, Ap tail | - consCand(head, tail, config) and - ap = apCons(head, tail) - ) - } - - additional predicate consCand(TypedContent tc, Ap ap, Configuration config) { - revConsCand(tc, ap, config) and - validAp(ap, config) - } - - pragma[nomagic] - private predicate parameterFlowsThroughRev( - ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp, Configuration config - ) { - revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap, config) and - parameterFlowThroughAllowed(p, pos.getKind()) - } - - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(ReturnPosition pos | - returnFlowsThrough(_, pos, _, _, p, ap, _, config) and - parameterFlowsThroughRev(p, ap, pos, _, config) + exists(Content c | + revFlowIsReadAndStored(c) and + revFlow(node2) and + storeEx(node1, tc, node2, contentType) and + c = tc.getContent() and + exists(ap1) ) } pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - exists(ParamNodeEx p, ReturnPosition pos | - returnFlowsThrough(ret, pos, _, _, p, argAp, ap, config) and - parameterFlowsThroughRev(p, argAp, pos, ap, config) and - kind = pos.getKind() + predicate readStepCand(NodeEx n1, Content c, NodeEx n2) { + revFlowIsReadAndStored(c) and + read(n1, c, n2) and + revFlow(n2) + } + + pragma[nomagic] + predicate revFlow(NodeEx node) { revFlow(node, _) } + + pragma[nomagic] + predicate revFlowAp(NodeEx node, Ap ap) { + revFlow(node) and + exists(ap) + } + + bindingset[node, state] + predicate revFlow(NodeEx node, FlowState state, Ap ap) { + revFlow(node, _) and + exists(state) and + exists(ap) + } + + private predicate throughFlowNodeCand(NodeEx node) { + revFlow(node, true) and + fwdFlow(node, true) and + not inBarrier(node) and + not outBarrier(node) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand(DataFlowCallable callable, ReturnKindExt kind) { + exists(RetNodeEx ret | + throughFlowNodeCand(ret) and + callable = ret.getEnclosingCallable() and + kind = ret.getKind() + ) + } + + /** + * Holds if flow may enter through `p` and reach a return node making `p` a + * candidate for the origin of a summary. + */ + pragma[nomagic] + predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap) { + exists(DataFlowCallable c, ReturnKindExt kind | + throughFlowNodeCand(p) and + returnFlowCallableNodeCand(c, kind) and + p.getEnclosingCallable() = c and + exists(ap) and + parameterFlowThroughAllowed(p, kind) ) } pragma[nomagic] - private predicate revFlowThroughArg( - DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, - Ap ap, Configuration config - ) { - exists(ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp, config) - ) + predicate returnMayFlowThrough(RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind) { + throughFlowNodeCand(ret) and + kind = ret.getKind() and + exists(argAp) and + exists(ap) } pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | - revFlow(arg, state, returnCtx, returnAp, ap, config) and - revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap, config) + predicate callMayFlowThroughRev(DataFlowCall call) { + exists(ArgNodeEx arg, boolean toReturn | + revFlow(arg, toReturn) and + revFlowInToReturn(call, arg) and + revFlowIsReturned(call, toReturn) ) } additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config + boolean fwd, int nodes, int fields, int conscand, int states, int tuples ) { fwd = true and - nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, config)) and - fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and - states = count(FlowState state | fwdFlow(_, state, _, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | - fwdFlow(n, state, cc, summaryCtx, argAp, ap, config) - ) + nodes = count(NodeEx node | fwdFlow(node)) and + fields = count(Content f0 | fwdFlowConsCand(f0)) and + conscand = -1 and + states = count(FlowState state | fwdFlowState(state)) and + tuples = count(NodeEx n, boolean b | fwdFlow(n, b)) or fwd = false and - nodes = count(NodeEx node | revFlow(node, _, _, _, _, config)) and - fields = count(TypedContent f0 | consCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and - states = count(FlowState state | revFlow(_, state, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | - revFlow(n, state, returnCtx, retAp, ap, config) - ) + nodes = count(NodeEx node | revFlow(node, _)) and + fields = count(Content f0 | revFlowConsCand(f0)) and + conscand = -1 and + states = count(FlowState state | revFlowState(state)) and + tuples = count(NodeEx n, boolean b | revFlow(n, b)) } - /* End: Stage logic. */ - } -} - -private module BooleanCallContext { - class Cc extends boolean { - Cc() { this in [true, false] } + /* End: Stage 1 logic. */ } - class CcCall extends Cc { - CcCall() { this = true } + pragma[noinline] + private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2) { + Stage1::revFlow(node2) and + localFlowStepEx(node1, node2) } - /** Holds if the call context may be `call`. */ - predicate matchesCall(CcCall cc, DataFlowCall call) { any() } - - class CcNoCall extends Cc { - CcNoCall() { this = false } + pragma[noinline] + private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2) { + Stage1::revFlow(node2) and + additionalLocalFlowStep(node1, node2) } - Cc ccNone() { result = false } + pragma[nomagic] + private predicate viableReturnPosOutNodeCand1(DataFlowCall call, ReturnPosition pos, NodeEx out) { + Stage1::revFlow(out) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out) + } - CcCall ccSomeCall() { result = true } + /** + * Holds if data can flow out of `call` from `ret` to `out`, either + * through a `ReturnNode` or through an argument that has been mutated, and + * that this step is part of a path from a source to a sink. + */ + pragma[nomagic] + private predicate flowOutOfCallNodeCand1( + DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out + ) { + exists(ReturnPosition pos | + viableReturnPosOutNodeCand1(call, pos, out) and + pos = ret.getReturnPosition() and + kind = pos.getKind() and + Stage1::revFlow(ret) and + not outBarrier(ret) and + not inBarrier(out) + ) + } - class LocalCc = Unit; + pragma[nomagic] + private predicate viableParamArgNodeCand1(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { + Stage1::viableParamArgNodeCandFwd1(call, p, arg) and + Stage1::revFlow(arg) + } - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } + /** + * Holds if data can flow into `call` and that this step is part of a + * path from a source to a sink. + */ + pragma[nomagic] + private predicate flowIntoCallNodeCand1(DataFlowCall call, ArgNodeEx arg, ParamNodeEx p) { + viableParamArgNodeCand1(call, p, arg) and + Stage1::revFlow(p) and + not outBarrier(arg) and + not inBarrier(p) + } - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + /** + * Gets the amount of forward branching on the origin of a cross-call path + * edge in the graph of paths between sources and sinks that ignores call + * contexts. + */ + pragma[nomagic] + private int branch(NodeEx n1) { + result = + strictcount(NodeEx n | flowOutOfCallNodeCand1(_, n1, _, n) or flowIntoCallNodeCand1(_, n1, n)) + } - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } -} + /** + * Gets the amount of backward branching on the target of a cross-call path + * edge in the graph of paths between sources and sinks that ignores call + * contexts. + */ + pragma[nomagic] + private int join(NodeEx n2) { + result = + strictcount(NodeEx n | flowOutOfCallNodeCand1(_, n, _, n2) or flowIntoCallNodeCand1(_, n, n2)) + } -private module Level1CallContext { - class Cc = CallContext; + /** + * Holds if data can flow out of `call` from `ret` to `out`, either + * through a `ReturnNode` or through an argument that has been mutated, and + * that this step is part of a path from a source to a sink. The + * `allowsFieldFlow` flag indicates whether the branching is within the limit + * specified by the configuration. + */ + pragma[nomagic] + private predicate flowOutOfCallNodeCand1( + DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow + ) { + flowOutOfCallNodeCand1(call, ret, kind, out) and + exists(int b, int j | + b = branch(ret) and + j = join(out) and + if b.minimum(j) <= Config::fieldFlowBranchLimit() + then allowsFieldFlow = true + else allowsFieldFlow = false + ) + } - class CcCall = CallContextCall; + /** + * Holds if data can flow into `call` and that this step is part of a + * path from a source to a sink. The `allowsFieldFlow` flag indicates whether + * the branching is within the limit specified by the configuration. + */ + pragma[nomagic] + private predicate flowIntoCallNodeCand1( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow + ) { + flowIntoCallNodeCand1(call, arg, p) and + exists(int b, int j | + b = branch(arg) and + j = join(p) and + if b.minimum(j) <= Config::fieldFlowBranchLimit() + then allowsFieldFlow = true + else allowsFieldFlow = false + ) + } - pragma[inline] - predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } + private signature module StageSig { + class Ap; - class CcNoCall = CallContextNoCall; + predicate revFlow(NodeEx node); - Cc ccNone() { result instanceof CallContextAny } + predicate revFlowAp(NodeEx node, Ap ap); - CcCall ccSomeCall() { result instanceof CallContextSomeCall } + bindingset[node, state] + predicate revFlow(NodeEx node, FlowState state, Ap ap); + + predicate callMayFlowThroughRev(DataFlowCall call); + + predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap); + + predicate returnMayFlowThrough(RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind); + + predicate storeStepCand( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType + ); + + predicate readStepCand(NodeEx n1, Content c, NodeEx n2); + } + + private module MkStage { + class ApApprox = PrevStage::Ap; + + signature module StageParam { + class Ap; + + class ApNil extends Ap; + + bindingset[result, ap] + ApApprox getApprox(Ap ap); + + ApNil getApNil(NodeEx node); + + bindingset[tc, tail] + Ap apCons(TypedContent tc, Ap tail); + + /** + * An approximation of `Content` that corresponds to the precision level of + * `Ap`, such that the mappings from both `Ap` and `Content` to this type + * are functional. + */ + class ApHeadContent; + + ApHeadContent getHeadContent(Ap ap); + + ApHeadContent projectToHeadContent(Content c); + + class ApOption; + + ApOption apNone(); + + ApOption apSome(Ap ap); + + class Cc; + + class CcCall extends Cc; + + // TODO: member predicate on CcCall + predicate matchesCall(CcCall cc, DataFlowCall call); + + class CcNoCall extends Cc; + + Cc ccNone(); + + CcCall ccSomeCall(); + + class LocalCc; + + bindingset[call, c, outercc] + CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); + + bindingset[call, c, innercc] + CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); + + bindingset[node, cc] + LocalCc getLocalCc(NodeEx node, Cc cc); + + bindingset[node1, state1] + bindingset[node2, state2] + predicate localStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + ApNil ap, LocalCc lcc + ); + + predicate flowOutOfCall( + DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow + ); + + predicate flowIntoCall( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow + ); + + bindingset[node, state, ap] + predicate filter(NodeEx node, FlowState state, Ap ap); + + bindingset[ap, contentType] + predicate typecheckStore(Ap ap, DataFlowType contentType); + } + + module Stage implements StageSig { + import Param + + /* Begin: Stage logic. */ + // use an alias as a workaround for bad functionality-induced joins + pragma[nomagic] + private predicate revFlowApAlias(NodeEx node, ApApprox apa) { + PrevStage::revFlowAp(node, apa) + } + + pragma[nomagic] + private predicate flowIntoCallApa( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa + ) { + flowIntoCall(call, arg, p, allowsFieldFlow) and + PrevStage::revFlowAp(p, pragma[only_bind_into](apa)) and + revFlowApAlias(arg, pragma[only_bind_into](apa)) + } + + pragma[nomagic] + private predicate flowOutOfCallApa( + DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, + ApApprox apa + ) { + flowOutOfCall(call, ret, kind, out, allowsFieldFlow) and + PrevStage::revFlowAp(out, pragma[only_bind_into](apa)) and + revFlowApAlias(ret, pragma[only_bind_into](apa)) + } + + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, + ApApprox argApa, ApApprox apa + ) { + exists(ReturnKindExt kind | + flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa) and + PrevStage::callMayFlowThroughRev(call) and + PrevStage::returnMayFlowThrough(ret, argApa, apa, kind) and + matchesCall(ccc, call) + ) + } + + /** + * Holds if `node` is reachable with access path `ap` from a source. + * + * The call context `cc` records whether the node is reached through an + * argument in a call, and if so, `summaryCtx` and `argAp` record the + * corresponding parameter position and access path of that argument, respectively. + */ + pragma[nomagic] + additional predicate fwdFlow( + NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, + ApApprox apa + ) { + fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa) and + PrevStage::revFlow(node, state, apa) and + filter(node, state, ap) + } + + pragma[inline] + additional predicate fwdFlow( + NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap + ) { + fwdFlow(node, state, cc, summaryCtx, argAp, ap, _) + } + + pragma[assume_small_delta] + pragma[nomagic] + private predicate fwdFlow0( + NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, + ApApprox apa + ) { + sourceNode(node, state) and + (if hasSourceCallCtx() then cc = ccSomeCall() else cc = ccNone()) and + argAp = apNone() and + summaryCtx = TParamNodeNone() and + ap = getApNil(node) and + apa = getApprox(ap) + or + exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | + fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0) and + localCc = getLocalCc(mid, cc) + | + localStep(mid, state0, node, state, true, _, localCc) and + ap = ap0 and + apa = apa0 + or + localStep(mid, state0, node, state, false, ap, localCc) and + ap0 instanceof ApNil and + apa = getApprox(ap) + ) + or + exists(NodeEx mid | + fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa) and + jumpStepEx(mid, node) and + cc = ccNone() and + summaryCtx = TParamNodeNone() and + argAp = apNone() + ) + or + exists(NodeEx mid, ApNil nil | + fwdFlow(mid, state, _, _, _, nil) and + additionalJumpStep(mid, node) and + cc = ccNone() and + summaryCtx = TParamNodeNone() and + argAp = apNone() and + ap = getApNil(node) and + apa = getApprox(ap) + ) + or + exists(NodeEx mid, FlowState state0, ApNil nil | + fwdFlow(mid, state0, _, _, _, nil) and + additionalJumpStateStep(mid, state0, node, state) and + cc = ccNone() and + summaryCtx = TParamNodeNone() and + argAp = apNone() and + ap = getApNil(node) and + apa = getApprox(ap) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp) and + ap = apCons(tc, ap0) and + apa = getApprox(ap) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp) and + fwdFlowConsCand(ap0, c, ap) and + apa = getApprox(ap) + ) + or + // flow into a callable + fwdFlowIn(_, node, state, _, cc, _, _, ap, apa) and + if PrevStage::parameterMayFlowThrough(node, apa) + then ( + summaryCtx = TParamNodeSome(node.asNode()) and + argAp = apSome(ap) + ) else ( + summaryCtx = TParamNodeNone() and argAp = apNone() + ) + or + // flow out of a callable + exists( + DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | + fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa) and + flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa) and + inner = ret.getEnclosingCallable() and + cc = getCallContextReturn(inner, call, innercc) and + if allowsFieldFlow = false then ap instanceof ApNil else any() + ) + or + // flow through a callable + exists( + DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, + ApApprox innerArgApa + | + fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa) and + flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa) and + if allowsFieldFlow = false then ap instanceof ApNil else any() + ) + } + + pragma[nomagic] + private predicate fwdFlowStore( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, + ParamNodeOption summaryCtx, ApOption argAp + ) { + exists(DataFlowType contentType, ApApprox apa1 | + fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1) and + PrevStage::storeStepCand(node1, apa1, tc, node2, contentType) and + typecheckStore(ap1, contentType) + ) + } + + /** + * Holds if forward flow with access path `tail` reaches a store of `c` + * resulting in access path `cons`. + */ + pragma[nomagic] + private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail) { + exists(TypedContent tc | + fwdFlowStore(_, tail, tc, _, _, _, _, _) and + tc.getContent() = c and + cons = apCons(tc, tail) + ) + } + + pragma[nomagic] + private predicate readStepCand(NodeEx node1, ApHeadContent apc, Content c, NodeEx node2) { + PrevStage::readStepCand(node1, c, node2) and + apc = projectToHeadContent(c) + } + + bindingset[node1, apc] + pragma[inline_late] + private predicate readStepCand0(NodeEx node1, ApHeadContent apc, Content c, NodeEx node2) { + readStepCand(node1, apc, c, node2) + } + + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, + ParamNodeOption summaryCtx, ApOption argAp + ) { + exists(ApHeadContent apc | + fwdFlow(node1, state, cc, summaryCtx, argAp, ap) and + apc = getHeadContent(ap) and + readStepCand0(node1, apc, c, node2) + ) + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, + ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa + ) { + exists(ArgNodeEx arg, boolean allowsFieldFlow | + fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa) and + flowIntoCallApa(call, arg, p, allowsFieldFlow, apa) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and + if allowsFieldFlow = false then ap instanceof ApNil else any() + ) + } + + pragma[nomagic] + private predicate fwdFlowRetFromArg( + RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, + ApApprox argApa, Ap ap, ApApprox apa + ) { + exists(ReturnKindExt kind | + fwdFlow(pragma[only_bind_into](ret), state, ccc, + TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), + pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa)) and + kind = ret.getKind() and + parameterFlowThroughAllowed(summaryCtx, kind) and + argApa = getApprox(argAp) and + PrevStage::returnMayFlowThrough(ret, argApa, apa, kind) + ) + } + + pragma[inline] + private predicate fwdFlowThrough0( + DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, + ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, + Ap innerArgAp, ApApprox innerArgApa + ) { + fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa) and + fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp) + } + + pragma[nomagic] + private predicate fwdFlowThrough( + DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, + ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa + ) { + fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa) + } + + /** + * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` + * and data might flow through the target callable and back out at `call`. + */ + pragma[nomagic] + private predicate fwdFlowIsEntered( + DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, + ParamNodeEx p, Ap ap + ) { + exists(ApApprox apa | + fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, + pragma[only_bind_into](apa)) and + PrevStage::parameterMayFlowThrough(p, apa) and + PrevStage::callMayFlowThroughRev(call) + ) + } + + pragma[nomagic] + private predicate storeStepFwd(NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2) { + fwdFlowStore(node1, ap1, tc, node2, _, _, _, _) and + ap2 = apCons(tc, ap1) and + fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _) + } + + private predicate readStepFwd(NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2) { + fwdFlowRead(ap1, c, n1, n2, _, _, _, _) and + fwdFlowConsCand(ap1, c, ap2) + } + + pragma[nomagic] + private predicate returnFlowsThrough0( + DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, + ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa + ) { + fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, + innerArgApa) + } + + pragma[nomagic] + private predicate returnFlowsThrough( + RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, + Ap ap + ) { + exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | + returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa) and + flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa) and + pos = ret.getReturnPosition() and + if allowsFieldFlow = false then ap instanceof ApNil else any() + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap + ) { + exists(ApApprox argApa | + flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), + allowsFieldFlow, argApa) and + fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa) and + returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap) and + if allowsFieldFlow = false then argAp instanceof ApNil else any() + ) + } + + pragma[nomagic] + private predicate flowIntoCallAp( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap + ) { + exists(ApApprox apa | + flowIntoCallApa(call, arg, p, allowsFieldFlow, apa) and + fwdFlow(arg, _, _, _, _, ap, apa) + ) + } + + pragma[nomagic] + private predicate flowOutOfCallAp( + DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, + Ap ap + ) { + exists(ApApprox apa | + flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa) and + fwdFlow(ret, _, _, _, _, ap, apa) and + pos = ret.getReturnPosition() + ) + } + + /** + * Holds if `node` with access path `ap` is part of a path from a source to a + * sink. + * + * The parameter `returnCtx` records whether (and how) the node must be returned + * from the enclosing callable in order to reach a sink, and if so, `returnAp` + * records the access path of the returned value. + */ + pragma[nomagic] + additional predicate revFlow( + NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap + ) { + revFlow0(node, state, returnCtx, returnAp, ap) and + fwdFlow(node, state, _, _, _, ap) + } + + pragma[nomagic] + private predicate revFlow0( + NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap + ) { + fwdFlow(node, state, _, _, _, ap) and + sinkNode(node, state) and + ( + if hasSinkCallCtx() + then returnCtx = TReturnCtxNoFlowThrough() + else returnCtx = TReturnCtxNone() + ) and + returnAp = apNone() and + ap instanceof ApNil + or + exists(NodeEx mid, FlowState state0 | + localStep(node, state, mid, state0, true, _, _) and + revFlow(mid, state0, returnCtx, returnAp, ap) + ) + or + exists(NodeEx mid, FlowState state0, ApNil nil | + fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap) and + localStep(node, pragma[only_bind_into](state), mid, state0, false, _, _) and + revFlow(mid, state0, returnCtx, returnAp, nil) and + ap instanceof ApNil + ) + or + exists(NodeEx mid | + jumpStepEx(node, mid) and + revFlow(mid, state, _, _, ap) and + returnCtx = TReturnCtxNone() and + returnAp = apNone() + ) + or + exists(NodeEx mid, ApNil nil | + fwdFlow(node, _, _, _, _, ap) and + additionalJumpStep(node, mid) and + revFlow(pragma[only_bind_into](mid), state, _, _, nil) and + returnCtx = TReturnCtxNone() and + returnAp = apNone() and + ap instanceof ApNil + ) + or + exists(NodeEx mid, FlowState state0, ApNil nil | + fwdFlow(node, _, _, _, _, ap) and + additionalJumpStateStep(node, state, mid, state0) and + revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil) and + returnCtx = TReturnCtxNone() and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp) and + revFlowConsCand(ap0, c, ap) + ) + or + // read + exists(NodeEx mid, Ap ap0 | + revFlow(mid, state, returnCtx, returnAp, ap0) and + readStepFwd(node, ap, _, mid, ap0) + ) + or + // flow into a callable + exists(ParamNodeEx p, boolean allowsFieldFlow | + revFlow(p, state, TReturnCtxNone(), returnAp, ap) and + flowIntoCallAp(_, node, p, allowsFieldFlow, ap) and + (if allowsFieldFlow = false then ap instanceof ApNil else any()) and + returnCtx = TReturnCtxNone() + ) + or + // flow through a callable + exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | + revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp) and + flowThroughIntoCall(call, node, p, _, ap, innerReturnAp) + ) + or + // flow out of a callable + exists(ReturnPosition pos | + revFlowOut(_, node, pos, state, _, _, ap) and + if returnFlowsThrough(node, pos, state, _, _, _, ap) + then ( + returnCtx = TReturnCtxMaybeFlowThrough(pos) and + returnAp = apSome(ap) + ) else ( + returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() + ) + ) + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, + ReturnCtx returnCtx, ApOption returnAp + ) { + revFlow(mid, state, returnCtx, returnAp, ap0) and + storeStepFwd(node, ap, tc, mid, ap0) and + tc.getContent() = c + } + + /** + * Holds if reverse flow with access path `tail` reaches a read of `c` + * resulting in access path `cons`. + */ + pragma[nomagic] + private predicate revFlowConsCand(Ap cons, Content c, Ap tail) { + exists(NodeEx mid, Ap tail0 | + revFlow(mid, _, _, _, tail) and + tail = pragma[only_bind_into](tail0) and + readStepFwd(_, cons, c, mid, tail0) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, + ApOption returnAp, Ap ap + ) { + exists(NodeEx out, boolean allowsFieldFlow | + revFlow(out, state, returnCtx, returnAp, ap) and + flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap) and + if allowsFieldFlow = false then ap instanceof ApNil else any() + ) + } + + pragma[nomagic] + private predicate revFlowParamToReturn( + ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap + ) { + revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), + pragma[only_bind_into](ap)) and + parameterFlowThroughAllowed(p, pos.getKind()) and + PrevStage::parameterMayFlowThrough(p, getApprox(ap)) + } + + pragma[nomagic] + private predicate revFlowThrough( + DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, + ApOption returnAp, Ap ap, Ap innerReturnAp + ) { + revFlowParamToReturn(p, state, pos, innerReturnAp, ap) and + revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. + */ + pragma[nomagic] + private predicate revFlowIsReturned( + DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap + ) { + exists(RetNodeEx ret, FlowState state, CcCall ccc | + revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap) and + returnFlowsThrough(ret, pos, state, ccc, _, _, ap) and + matchesCall(ccc, call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType + ) { + exists(Ap ap2, Content c | + PrevStage::storeStepCand(node1, _, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _) and + revFlowConsCand(ap2, c, ap1) + ) + } + + predicate readStepCand(NodeEx node1, Content c, NodeEx node2) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, _, pragma[only_bind_into](ap2)) and + readStepFwd(node1, ap1, c, node2, ap2) and + revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _) + ) + } + + additional predicate revFlow(NodeEx node, FlowState state) { revFlow(node, state, _, _, _) } + + predicate revFlow(NodeEx node, FlowState state, Ap ap) { revFlow(node, state, _, _, ap) } + + pragma[nomagic] + predicate revFlow(NodeEx node) { revFlow(node, _, _, _, _) } + + pragma[nomagic] + predicate revFlowAp(NodeEx node, Ap ap) { revFlow(node, _, _, _, ap) } + + // use an alias as a workaround for bad functionality-induced joins + pragma[nomagic] + additional predicate revFlowAlias(NodeEx node) { revFlow(node, _, _, _, _) } + + // use an alias as a workaround for bad functionality-induced joins + pragma[nomagic] + additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap) { + revFlow(node, state, ap) + } + + private predicate fwdConsCand(TypedContent tc, Ap ap) { storeStepFwd(_, ap, tc, _, _) } + + private predicate revConsCand(TypedContent tc, Ap ap) { storeStepCand(_, ap, tc, _, _) } + + private predicate validAp(Ap ap) { + revFlow(_, _, _, _, ap) and ap instanceof ApNil + or + exists(TypedContent head, Ap tail | + consCand(head, tail) and + ap = apCons(head, tail) + ) + } + + additional predicate consCand(TypedContent tc, Ap ap) { + revConsCand(tc, ap) and + validAp(ap) + } + + pragma[nomagic] + private predicate parameterFlowsThroughRev( + ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp + ) { + revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap) and + parameterFlowThroughAllowed(p, pos.getKind()) + } + + pragma[nomagic] + predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap) { + exists(ReturnPosition pos | + returnFlowsThrough(_, pos, _, _, p, ap, _) and + parameterFlowsThroughRev(p, ap, pos, _) + ) + } + + pragma[nomagic] + predicate returnMayFlowThrough(RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind) { + exists(ParamNodeEx p, ReturnPosition pos | + returnFlowsThrough(ret, pos, _, _, p, argAp, ap) and + parameterFlowsThroughRev(p, argAp, pos, ap) and + kind = pos.getKind() + ) + } + + pragma[nomagic] + private predicate revFlowThroughArg( + DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, + Ap ap + ) { + exists(ParamNodeEx p, Ap innerReturnAp | + revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp) and + flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp) + ) + } + + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call) { + exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | + revFlow(arg, state, returnCtx, returnAp, ap) and + revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap) + ) + } + + additional predicate stats( + boolean fwd, int nodes, int fields, int conscand, int states, int tuples + ) { + fwd = true and + nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap)) and + states = count(FlowState state | fwdFlow(_, state, _, _, _, _)) and + tuples = + count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | + fwdFlow(n, state, cc, summaryCtx, argAp, ap) + ) + or + fwd = false and + nodes = count(NodeEx node | revFlow(node, _, _, _, _)) and + fields = count(TypedContent f0 | consCand(f0, _)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap)) and + states = count(FlowState state | revFlow(_, state, _, _, _)) and + tuples = + count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | + revFlow(n, state, returnCtx, retAp, ap) + ) + } + /* End: Stage logic. */ + } + } + + private module BooleanCallContext { + class Cc extends boolean { + Cc() { this in [true, false] } + } + + class CcCall extends Cc { + CcCall() { this = true } + } + + /** Holds if the call context may be `call`. */ + predicate matchesCall(CcCall cc, DataFlowCall call) { any() } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccNone() { result = false } + + CcCall ccSomeCall() { result = true } - module NoLocalCallContext { class LocalCc = Unit; bindingset[node, cc] LocalCc getLocalCc(NodeEx node, Cc cc) { any() } bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSiteDispatch(call, c) - then result = TSpecificCall(call) - else result = TSomeCall() + CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c, innercc] + CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } + } + + private module Level1CallContext { + class Cc = CallContext; + + class CcCall = CallContextCall; + + pragma[inline] + predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } + + class CcNoCall = CallContextNoCall; + + Cc ccNone() { result instanceof CallContextAny } + + CcCall ccSomeCall() { result instanceof CallContextSomeCall } + + module NoLocalCallContext { + class LocalCc = Unit; + + bindingset[node, cc] + LocalCc getLocalCc(NodeEx node, Cc cc) { any() } + + bindingset[call, c, outercc] + CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + checkCallContextCall(outercc, call, c) and + if recordDataFlowCallSiteDispatch(call, c) + then result = TSpecificCall(call) + else result = TSomeCall() + } + } + + module LocalCallContext { + class LocalCc = LocalCallContext; + + bindingset[node, cc] + LocalCc getLocalCc(NodeEx node, Cc cc) { + result = + getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), + node.getEnclosingCallable()) + } + + bindingset[call, c, outercc] + CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + checkCallContextCall(outercc, call, c) and + if recordDataFlowCallSite(call, c) + then result = TSpecificCall(call) + else result = TSomeCall() + } + } + + bindingset[call, c, innercc] + CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { + checkCallContextReturn(innercc, c, call) and + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() } } - module LocalCallContext { - class LocalCc = LocalCallContext; + private module Stage2Param implements MkStage::StageParam { + private module PrevStage = Stage1; - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { - result = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - node.getEnclosingCallable()) + class Ap extends boolean { + Ap() { this in [true, false] } } - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + class ApNil extends Ap { + ApNil() { this = false } } + + bindingset[result, ap] + PrevStage::Ap getApprox(Ap ap) { any() } + + ApNil getApNil(NodeEx node) { Stage1::revFlow(node) and exists(result) } + + bindingset[tc, tail] + Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + class ApHeadContent = Unit; + + pragma[inline] + ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } + + ApHeadContent projectToHeadContent(Content c) { any() } + + class ApOption = BooleanOption; + + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + + import Level1CallContext + import NoLocalCallContext + + bindingset[node1, state1] + bindingset[node2, state2] + predicate localStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + ApNil ap, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2) and + state1 = state2 + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2) and + state1 = state2 + or + preservesValue = false and + additionalLocalStateStep(node1, state1, node2, state2) + ) and + exists(ap) and + exists(lcc) + } + + predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + predicate flowIntoCall = flowIntoCallNodeCand1/4; + + pragma[nomagic] + private predicate expectsContentCand(NodeEx node) { + exists(Content c | + PrevStage::revFlow(node) and + PrevStage::revFlowIsReadAndStored(c) and + expectsContentEx(node, c) + ) + } + + bindingset[node, state, ap] + predicate filter(NodeEx node, FlowState state, Ap ap) { + PrevStage::revFlowState(state) and + exists(ap) and + not stateBarrier(node, state) and + ( + notExpectsContent(node) + or + ap = true and + expectsContentCand(node) + ) + } + + bindingset[ap, contentType] + predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } } - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { - checkCallContextReturn(innercc, c, call) and - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() + private module Stage2 implements StageSig { + import MkStage::Stage } -} - -private module Stage2Param implements MkStage::StageParam { - private module PrevStage = Stage1; - - class Ap extends boolean { - Ap() { this in [true, false] } - } - - class ApNil extends Ap { - ApNil() { this = false } - } - - bindingset[result, ap] - PrevStage::Ap getApprox(Ap ap) { any() } - - ApNil getApNil(NodeEx node) { Stage1::revFlow(node, _) and exists(result) } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } - - class ApHeadContent = Unit; - - pragma[inline] - ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } - - ApHeadContent projectToHeadContent(Content c) { any() } - - class ApOption = BooleanOption; - - ApOption apNone() { result = TBooleanNone() } - - ApOption apSome(Ap ap) { result = TBooleanSome(ap) } - - import Level1CallContext - import NoLocalCallContext - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - ( - preservesValue = true and - localFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalStateStep(node1, state1, node2, state2, config) - ) and - exists(ap) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand1/6; - - predicate flowIntoCall = flowIntoCallNodeCand1/5; pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - expectsContentEx(node, c) - ) + private predicate flowOutOfCallNodeCand2( + DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow + ) { + flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow) and + Stage2::revFlow(node2) and + Stage2::revFlowAlias(node1) } - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - PrevStage::revFlowState(state, pragma[only_bind_into](config)) and - exists(ap) and - not stateBarrier(node, state, config) and - ( - notExpectsContent(node) - or - ap = true and - expectsContentCand(node, config) - ) + pragma[nomagic] + private predicate flowIntoCallNodeCand2( + DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow + ) { + flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow) and + Stage2::revFlow(node2) and + Stage2::revFlowAlias(node1) } - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage2 implements StageSig { - import MkStage::Stage -} - -pragma[nomagic] -private predicate flowOutOfCallNodeCand2( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -pragma[nomagic] -private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -private module LocalFlowBigStep { - /** - * A node where some checking is required, and hence the big-step relation - * is not allowed to step over. - */ - private class FlowCheckNode extends NodeEx { - FlowCheckNode() { - castNode(this.asNode()) or - clearsContentCached(this.asNode(), _) or - expectsContentCached(this.asNode(), _) + private module LocalFlowBigStep { + /** + * A node where some checking is required, and hence the big-step relation + * is not allowed to step over. + */ + private class FlowCheckNode extends NodeEx { + FlowCheckNode() { + castNode(this.asNode()) or + clearsContentCached(this.asNode(), _) or + expectsContentCached(this.asNode(), _) + } } - } - /** - * Holds if `node` can be the first node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowEntry(NodeEx node, FlowState state, Configuration config) { - Stage2::revFlow(node, state, config) and - ( - sourceNode(node, state, config) + /** + * Holds if `node` can be the first node in a maximal subsequence of local + * flow steps in a dataflow path. + */ + private predicate localFlowEntry(NodeEx node, FlowState state) { + Stage2::revFlow(node, state) and + ( + sourceNode(node, state) + or + jumpStepEx(_, node) + or + additionalJumpStep(_, node) + or + additionalJumpStateStep(_, _, node, state) + or + node instanceof ParamNodeEx + or + node.asNode() instanceof OutNodeExt + or + Stage2::storeStepCand(_, _, _, node, _) + or + Stage2::readStepCand(_, _, node) + or + node instanceof FlowCheckNode + or + exists(FlowState s | + additionalLocalStateStep(_, s, node, state) and + s != state + ) + ) + } + + /** + * Holds if `node` can be the last node in a maximal subsequence of local + * flow steps in a dataflow path. + */ + private predicate localFlowExit(NodeEx node, FlowState state) { + exists(NodeEx next | Stage2::revFlow(next, state) | + jumpStepEx(node, next) or + additionalJumpStep(node, next) or + flowIntoCallNodeCand2(_, node, next, _) or + flowOutOfCallNodeCand2(_, node, _, next, _) or + Stage2::storeStepCand(node, _, _, next, _) or + Stage2::readStepCand(node, _, next) + ) or - jumpStep(_, node, config) - or - additionalJumpStep(_, node, config) - or - additionalJumpStateStep(_, _, node, state, config) - or - node instanceof ParamNodeEx - or - node.asNode() instanceof OutNodeExt - or - Stage2::storeStepCand(_, _, _, node, _, config) - or - Stage2::readStepCand(_, _, node, config) - or - node instanceof FlowCheckNode - or - exists(FlowState s | - additionalLocalStateStep(_, s, node, state, config) and + exists(NodeEx next, FlowState s | Stage2::revFlow(next, s) | + additionalJumpStateStep(node, state, next, s) + or + additionalLocalStateStep(node, state, next, s) and s != state ) - ) - } - - /** - * Holds if `node` can be the last node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowExit(NodeEx node, FlowState state, Configuration config) { - exists(NodeEx next | Stage2::revFlow(next, state, config) | - jumpStep(node, next, config) or - additionalJumpStep(node, next, config) or - flowIntoCallNodeCand2(_, node, next, _, config) or - flowOutOfCallNodeCand2(_, node, _, next, _, config) or - Stage2::storeStepCand(node, _, _, next, _, config) or - Stage2::readStepCand(node, _, next, config) - ) - or - exists(NodeEx next, FlowState s | Stage2::revFlow(next, s, config) | - additionalJumpStateStep(node, state, next, s, config) or - additionalLocalStateStep(node, state, next, s, config) and - s != state - ) - or - Stage2::revFlow(node, state, config) and - node instanceof FlowCheckNode - or - sinkNode(node, state, config) - } + Stage2::revFlow(node, state) and + node instanceof FlowCheckNode + or + sinkNode(node, state) + } - pragma[noinline] - private predicate additionalLocalFlowStepNodeCand2( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, Configuration config - ) { - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 and - Stage2::revFlow(node1, pragma[only_bind_into](state1), false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false, - pragma[only_bind_into](config)) - or - additionalLocalStateStep(node1, state1, node2, state2, config) and - Stage2::revFlow(node1, state1, false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, state2, false, pragma[only_bind_into](config)) - } + pragma[noinline] + private predicate additionalLocalFlowStepNodeCand2( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2 + ) { + additionalLocalFlowStepNodeCand1(node1, node2) and + state1 = state2 and + Stage2::revFlow(node1, pragma[only_bind_into](state1), false) and + Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false) + or + additionalLocalStateStep(node1, state1, node2, state2) and + Stage2::revFlow(node1, state1, false) and + Stage2::revFlowAlias(node2, state2, false) + } - /** - * Holds if the local path from `node1` to `node2` is a prefix of a maximal - * subsequence of local flow steps in a dataflow path. - * - * This is the transitive closure of `[additional]localFlowStep` beginning - * at `localFlowEntry`. - */ - pragma[nomagic] - private predicate localFlowStepPlus( - NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, - Configuration config, LocalCallContext cc - ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and - ( - localFlowEntry(node1, pragma[only_bind_into](state), pragma[only_bind_into](config)) and + /** + * Holds if the local path from `node1` to `node2` is a prefix of a maximal + * subsequence of local flow steps in a dataflow path. + * + * This is the transitive closure of `[additional]localFlowStep` beginning + * at `localFlowEntry`. + */ + pragma[nomagic] + private predicate localFlowStepPlus( + NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, + LocalCallContext cc + ) { + not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and ( - localFlowStepNodeCand1(node1, node2, config) and - preservesValue = true and - t = node1.getDataFlowType() and // irrelevant dummy value - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) + localFlowEntry(node1, pragma[only_bind_into](state)) and + ( + localFlowStepNodeCand1(node1, node2) and + preservesValue = true and + t = node1.getDataFlowType() and // irrelevant dummy value + Stage2::revFlow(node2, pragma[only_bind_into](state)) + or + additionalLocalFlowStepNodeCand2(node1, state, node2, state) and + preservesValue = false and + t = node2.getDataFlowType() + ) and + node1 != node2 and + cc.relevantFor(node1.getEnclosingCallable()) and + not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) or - additionalLocalFlowStepNodeCand2(node1, state, node2, state, config) and - preservesValue = false and - t = node2.getDataFlowType() - ) and - node1 != node2 and - cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) - or - exists(NodeEx mid | - localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, - pragma[only_bind_into](config), cc) and - localFlowStepNodeCand1(mid, node2, config) and - not mid instanceof FlowCheckNode and - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) + exists(NodeEx mid | + localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, cc) and + localFlowStepNodeCand1(mid, node2) and + not mid instanceof FlowCheckNode and + Stage2::revFlow(node2, pragma[only_bind_into](state)) + ) + or + exists(NodeEx mid | + localFlowStepPlus(node1, state, mid, _, _, cc) and + additionalLocalFlowStepNodeCand2(mid, state, node2, state) and + not mid instanceof FlowCheckNode and + preservesValue = false and + t = node2.getDataFlowType() + ) ) + } + + /** + * Holds if `node1` can step to `node2` in one or more local steps and this + * path can occur as a maximal subsequence of local steps in a dataflow path. + */ + pragma[nomagic] + predicate localFlowBigStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + DataFlowType t, LocalCallContext callContext + ) { + localFlowStepPlus(node1, state1, node2, preservesValue, t, callContext) and + localFlowExit(node2, state1) and + state1 = state2 or - exists(NodeEx mid | - localFlowStepPlus(node1, state, mid, _, _, pragma[only_bind_into](config), cc) and - additionalLocalFlowStepNodeCand2(mid, state, node2, state, config) and - not mid instanceof FlowCheckNode and - preservesValue = false and - t = node2.getDataFlowType() + additionalLocalFlowStepNodeCand2(node1, state1, node2, state2) and + state1 != state2 and + preservesValue = false and + t = node2.getDataFlowType() and + callContext.relevantFor(node1.getEnclosingCallable()) and + not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | + isUnreachableInCallCached(node1.asNode(), call) or + isUnreachableInCallCached(node2.asNode(), call) ) + } + } + + private import LocalFlowBigStep + + private module Stage3Param implements MkStage::StageParam { + private module PrevStage = Stage2; + + class Ap = ApproxAccessPathFront; + + class ApNil = ApproxAccessPathFrontNil; + + PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + ApNil getApNil(NodeEx node) { + PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) + } + + bindingset[tc, tail] + Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } + + class ApHeadContent = ContentApprox; + + pragma[noinline] + ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + predicate projectToHeadContent = getContentApprox/1; + + class ApOption = ApproxAccessPathFrontOption; + + ApOption apNone() { result = TApproxAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } + + import BooleanCallContext + + predicate localStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + ApproxAccessPathFrontNil ap, LocalCc lcc + ) { + localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), _) and + exists(lcc) + } + + predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + predicate flowIntoCall = flowIntoCallNodeCand2/4; + + pragma[nomagic] + private predicate expectsContentCand(NodeEx node, Ap ap) { + exists(Content c | + PrevStage::revFlow(node) and + PrevStage::readStepCand(_, c, _) and + expectsContentEx(node, c) and + c = ap.getAHead().getContent() + ) + } + + pragma[nomagic] + private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } + + bindingset[node, state, ap] + predicate filter(NodeEx node, FlowState state, Ap ap) { + exists(state) and + (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and + ( + notExpectsContent(node) + or + expectsContentCand(node, ap) + ) + } + + bindingset[ap, contentType] + predicate typecheckStore(Ap ap, DataFlowType contentType) { + // We need to typecheck stores here, since reverse flow through a getter + // might have a different type here compared to inside the getter. + compatibleTypes(ap.getType(), contentType) + } + } + + private module Stage3 implements StageSig { + import MkStage::Stage + } + + private module Stage4Param implements MkStage::StageParam { + private module PrevStage = Stage3; + + class Ap = AccessPathFront; + + class ApNil = AccessPathFrontNil; + + PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } + + ApNil getApNil(NodeEx node) { + PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) + } + + bindingset[tc, tail] + Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + class ApHeadContent = Content; + + pragma[noinline] + ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + ApHeadContent projectToHeadContent(Content c) { result = c } + + class ApOption = AccessPathFrontOption; + + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + + import BooleanCallContext + + pragma[nomagic] + predicate localStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + ApNil ap, LocalCc lcc + ) { + localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), _) and + PrevStage::revFlow(node1, pragma[only_bind_into](state1), _) and + PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _) and + exists(lcc) + } + + pragma[nomagic] + predicate flowOutOfCall( + DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow + ) { + exists(FlowState state | + flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow) and + PrevStage::revFlow(node2, pragma[only_bind_into](state), _) and + PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _) + ) + } + + pragma[nomagic] + predicate flowIntoCall( + DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow + ) { + exists(FlowState state | + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow) and + PrevStage::revFlow(node2, pragma[only_bind_into](state), _) and + PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _) + ) + } + + pragma[nomagic] + private predicate clearSet(NodeEx node, ContentSet c) { + PrevStage::revFlow(node) and + clearsContentCached(node.asNode(), c) + } + + pragma[nomagic] + private predicate clearContent(NodeEx node, Content c) { + exists(ContentSet cs | + PrevStage::readStepCand(_, pragma[only_bind_into](c), _) and + c = cs.getAReadContent() and + clearSet(node, cs) + ) + } + + pragma[nomagic] + private predicate clear(NodeEx node, Ap ap) { clearContent(node, ap.getHead().getContent()) } + + pragma[nomagic] + private predicate expectsContentCand(NodeEx node, Ap ap) { + exists(Content c | + PrevStage::revFlow(node) and + PrevStage::readStepCand(_, c, _) and + expectsContentEx(node, c) and + c = ap.getHead().getContent() + ) + } + + pragma[nomagic] + private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } + + bindingset[node, state, ap] + predicate filter(NodeEx node, FlowState state, Ap ap) { + exists(state) and + not clear(node, ap) and + (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and + ( + notExpectsContent(node) + or + expectsContentCand(node, ap) + ) + } + + bindingset[ap, contentType] + predicate typecheckStore(Ap ap, DataFlowType contentType) { + // We need to typecheck stores here, since reverse flow through a getter + // might have a different type here compared to inside the getter. + compatibleTypes(ap.getType(), contentType) + } + } + + private module Stage4 implements StageSig { + import MkStage::Stage + } + + /** + * Holds if `argApf` is recorded as the summary context for flow reaching `node` + * and remains relevant for the following pruning stage. + */ + private predicate flowCandSummaryCtx(NodeEx node, FlowState state, AccessPathFront argApf) { + exists(AccessPathFront apf | + Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf) and + Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf) ) } /** - * Holds if `node1` can step to `node2` in one or more local steps and this - * path can occur as a maximal subsequence of local steps in a dataflow path. + * Holds if a length 2 access path approximation with the head `tc` is expected + * to be expensive. */ - pragma[nomagic] - predicate localFlowBigStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - DataFlowType t, Configuration config, LocalCallContext callContext - ) { - localFlowStepPlus(node1, state1, node2, preservesValue, t, config, callContext) and - localFlowExit(node2, state1, config) and - state1 = state2 - or - additionalLocalFlowStepNodeCand2(node1, state1, node2, state2, config) and - state1 != state2 and - preservesValue = false and - t = node2.getDataFlowType() and - callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) - } -} - -private import LocalFlowBigStep - -private module Stage3Param implements MkStage::StageParam { - private module PrevStage = Stage2; - - class Ap = ApproxAccessPathFront; - - class ApNil = ApproxAccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } - - class ApHeadContent = ContentApprox; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - predicate projectToHeadContent = getContentApprox/1; - - class ApOption = ApproxAccessPathFrontOption; - - ApOption apNone() { result = TApproxAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } - - import BooleanCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApproxAccessPathFrontNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand2/6; - - predicate flowIntoCall = flowIntoCallNodeCand2/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getAHead().getContent() + private predicate expensiveLen2unfolding(TypedContent tc) { + exists(int tails, int nodes, int apLimit, int tupleLimit | + tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf)) and + nodes = + strictcount(NodeEx n, FlowState state | + Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc)) + or + flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc)) + ) and + accessPathApproxCostLimits(apLimit, tupleLimit) and + apLimit < tails and + tupleLimit < (tails - 1) * nodes and + not tc.forceHighPrecision() ) } - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } + private newtype TAccessPathApprox = + TNil(DataFlowType t) or + TConsNil(TypedContent tc, DataFlowType t) { + Stage4::consCand(tc, TFrontNil(t)) and + not expensiveLen2unfolding(tc) + } or + TConsCons(TypedContent tc1, TypedContent tc2, int len) { + Stage4::consCand(tc1, TFrontHead(tc2)) and + len in [2 .. accessPathLimit()] and + not expensiveLen2unfolding(tc1) + } or + TCons1(TypedContent tc, int len) { + len in [1 .. accessPathLimit()] and + expensiveLen2unfolding(tc) + } - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) + /** + * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only + * the first two elements of the list and its length are tracked. If data flows + * from a source to a given node with a given `AccessPathApprox`, this indicates + * the sequence of dereference operations needed to get from the value in the node + * to the tracked object. The final type indicates the type of the tracked object. + */ + abstract private class AccessPathApprox extends TAccessPathApprox { + abstract string toString(); + + abstract TypedContent getHead(); + + abstract int len(); + + abstract DataFlowType getType(); + + abstract AccessPathFront getFront(); + + /** Gets the access path obtained by popping `head` from this path, if any. */ + abstract AccessPathApprox pop(TypedContent head); } - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} + private class AccessPathApproxNil extends AccessPathApprox, TNil { + private DataFlowType t; -private module Stage3 implements StageSig { - import MkStage::Stage -} + AccessPathApproxNil() { this = TNil(t) } -private module Stage4Param implements MkStage::StageParam { - private module PrevStage = Stage3; + override string toString() { result = concat(": " + ppReprType(t)) } - class Ap = AccessPathFront; + override TypedContent getHead() { none() } - class ApNil = AccessPathFrontNil; + override int len() { result = 0 } - PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } + override DataFlowType getType() { result = t } - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) + override AccessPathFront getFront() { result = TFrontNil(t) } + + override AccessPathApprox pop(TypedContent head) { none() } } - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + abstract private class AccessPathApproxCons extends AccessPathApprox { } - class ApHeadContent = Content; + private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { + private TypedContent tc; + private DataFlowType t; - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } + AccessPathApproxConsNil() { this = TConsNil(tc, t) } - ApHeadContent projectToHeadContent(Content c) { result = c } + override string toString() { + // The `concat` becomes "" if `ppReprType` has no result. + result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) + } - class ApOption = AccessPathFrontOption; + override TypedContent getHead() { result = tc } - ApOption apNone() { result = TAccessPathFrontNone() } + override int len() { result = 1 } - ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + override DataFlowType getType() { result = tc.getContainerType() } - import BooleanCallContext + override AccessPathFront getFront() { result = TFrontHead(tc) } - pragma[nomagic] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) and - exists(lcc) + override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } } - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } + private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { + private TypedContent tc1; + private TypedContent tc2; + private int len; - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } + AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - pragma[nomagic] - private predicate clearSet(NodeEx node, ContentSet c, Configuration config) { - PrevStage::revFlow(node, config) and - clearsContentCached(node.asNode(), c) - } + override string toString() { + if len = 2 + then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" + else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" + } - pragma[nomagic] - private predicate clearContent(NodeEx node, Content c, Configuration config) { - exists(ContentSet cs | - PrevStage::readStepCand(_, pragma[only_bind_into](c), _, pragma[only_bind_into](config)) and - c = cs.getAReadContent() and - clearSet(node, cs, pragma[only_bind_into](config)) - ) - } + override TypedContent getHead() { result = tc1 } - pragma[nomagic] - private predicate clear(NodeEx node, Ap ap, Configuration config) { - clearContent(node, ap.getHead().getContent(), config) - } + override int len() { result = len } - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getHead().getContent() - ) - } + override DataFlowType getType() { result = tc1.getContainerType() } - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } + override AccessPathFront getFront() { result = TFrontHead(tc1) } - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - not clear(node, ap, config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage4 implements StageSig { - import MkStage::Stage -} - -/** - * Holds if `argApf` is recorded as the summary context for flow reaching `node` - * and remains relevant for the following pruning stage. - */ -private predicate flowCandSummaryCtx( - NodeEx node, FlowState state, AccessPathFront argApf, Configuration config -) { - exists(AccessPathFront apf | - Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf, config) and - Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf, - config) - ) -} - -/** - * Holds if a length 2 access path approximation with the head `tc` is expected - * to be expensive. - */ -private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { - exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf, config)) and - nodes = - strictcount(NodeEx n, FlowState state | - Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - or - flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - ) and - accessPathApproxCostLimits(apLimit, tupleLimit) and - apLimit < tails and - tupleLimit < (tails - 1) * nodes and - not tc.forceHighPrecision() - ) -} - -private newtype TAccessPathApprox = - TNil(DataFlowType t) or - TConsNil(TypedContent tc, DataFlowType t) { - Stage4::consCand(tc, TFrontNil(t), _) and - not expensiveLen2unfolding(tc, _) - } or - TConsCons(TypedContent tc1, TypedContent tc2, int len) { - Stage4::consCand(tc1, TFrontHead(tc2), _) and - len in [2 .. accessPathLimit()] and - not expensiveLen2unfolding(tc1, _) - } or - TCons1(TypedContent tc, int len) { - len in [1 .. accessPathLimit()] and - expensiveLen2unfolding(tc, _) - } - -/** - * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only - * the first two elements of the list and its length are tracked. If data flows - * from a source to a given node with a given `AccessPathApprox`, this indicates - * the sequence of dereference operations needed to get from the value in the node - * to the tracked object. The final type indicates the type of the tracked object. - */ -abstract private class AccessPathApprox extends TAccessPathApprox { - abstract string toString(); - - abstract TypedContent getHead(); - - abstract int len(); - - abstract DataFlowType getType(); - - abstract AccessPathFront getFront(); - - /** Gets the access path obtained by popping `head` from this path, if any. */ - abstract AccessPathApprox pop(TypedContent head); -} - -private class AccessPathApproxNil extends AccessPathApprox, TNil { - private DataFlowType t; - - AccessPathApproxNil() { this = TNil(t) } - - override string toString() { result = concat(": " + ppReprType(t)) } - - override TypedContent getHead() { none() } - - override int len() { result = 0 } - - override DataFlowType getType() { result = t } - - override AccessPathFront getFront() { result = TFrontNil(t) } - - override AccessPathApprox pop(TypedContent head) { none() } -} - -abstract private class AccessPathApproxCons extends AccessPathApprox { } - -private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { - private TypedContent tc; - private DataFlowType t; - - AccessPathApproxConsNil() { this = TConsNil(tc, t) } - - override string toString() { - // The `concat` becomes "" if `ppReprType` has no result. - result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) - } - - override TypedContent getHead() { result = tc } - - override int len() { result = 1 } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } -} - -private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { - private TypedContent tc1; - private TypedContent tc2; - private int len; - - AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - - override string toString() { - if len = 2 - then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" - else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc1 } - - override int len() { result = len } - - override DataFlowType getType() { result = tc1.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc1) } - - override AccessPathApprox pop(TypedContent head) { - head = tc1 and - ( - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - } -} - -private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { - private TypedContent tc; - private int len; - - AccessPathApproxCons1() { this = TCons1(tc, len) } - - override string toString() { - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc } - - override int len() { result = len } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { - head = tc and - ( - exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2), _) | + override AccessPathApprox pop(TypedContent head) { + head = tc1 and + ( result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -2763,1561 +2471,585 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or result = TCons1(tc2, len - 1) ) - or - exists(DataFlowType t | - len = 1 and - Stage4::consCand(tc, TFrontNil(t), _) and - result = TNil(t) + } + } + + private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { + private TypedContent tc; + private int len; + + AccessPathApproxCons1() { this = TCons1(tc, len) } + + override string toString() { + if len = 1 + then result = "[" + tc.toString() + "]" + else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" + } + + override TypedContent getHead() { result = tc } + + override int len() { result = len } + + override DataFlowType getType() { result = tc.getContainerType() } + + override AccessPathFront getFront() { result = TFrontHead(tc) } + + override AccessPathApprox pop(TypedContent head) { + head = tc and + ( + exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2)) | + result = TConsCons(tc2, _, len - 1) + or + len = 2 and + result = TConsNil(tc2, _) + or + result = TCons1(tc2, len - 1) + ) + or + exists(DataFlowType t | + len = 1 and + Stage4::consCand(tc, TFrontNil(t)) and + result = TNil(t) + ) ) - ) + } } -} -/** Gets the access path obtained by popping `tc` from `ap`, if any. */ -private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } + /** Gets the access path obtained by popping `tc` from `ap`, if any. */ + private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } -/** Gets the access path obtained by pushing `tc` onto `ap`. */ -private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } + /** Gets the access path obtained by pushing `tc` onto `ap`. */ + private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } -private newtype TAccessPathApproxOption = - TAccessPathApproxNone() or - TAccessPathApproxSome(AccessPathApprox apa) + private newtype TAccessPathApproxOption = + TAccessPathApproxNone() or + TAccessPathApproxSome(AccessPathApprox apa) -private class AccessPathApproxOption extends TAccessPathApproxOption { - string toString() { - this = TAccessPathApproxNone() and result = "" - or - this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) + private class AccessPathApproxOption extends TAccessPathApproxOption { + string toString() { + this = TAccessPathApproxNone() and result = "" + or + this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) + } } -} -private module Stage5Param implements MkStage::StageParam { - private module PrevStage = Stage4; + private module Stage5Param implements MkStage::StageParam { + private module PrevStage = Stage4; - class Ap = AccessPathApprox; + class Ap = AccessPathApprox; - class ApNil = AccessPathApproxNil; + class ApNil = AccessPathApproxNil; + + pragma[nomagic] + PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } + + ApNil getApNil(NodeEx node) { + PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) + } + + bindingset[tc, tail] + Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + class ApHeadContent = Content; + + pragma[noinline] + ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + ApHeadContent projectToHeadContent(Content c) { result = c } + + class ApOption = AccessPathApproxOption; + + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + + import Level1CallContext + import LocalCallContext + + predicate localStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + ApNil ap, LocalCc lcc + ) { + localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), lcc) and + PrevStage::revFlow(node1, pragma[only_bind_into](state1), _) and + PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _) + } + + pragma[nomagic] + predicate flowOutOfCall( + DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow + ) { + exists(FlowState state | + flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow) and + PrevStage::revFlow(node2, pragma[only_bind_into](state), _) and + PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _) + ) + } + + pragma[nomagic] + predicate flowIntoCall( + DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow + ) { + exists(FlowState state | + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow) and + PrevStage::revFlow(node2, pragma[only_bind_into](state), _) and + PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _) + ) + } + + bindingset[node, state, ap] + predicate filter(NodeEx node, FlowState state, Ap ap) { any() } + + // Type checking is not necessary here as it has already been done in stage 3. + bindingset[ap, contentType] + predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + } + + private module Stage5 = MkStage::Stage; pragma[nomagic] - PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathApproxOption; - - ApOption apNone() { result = TAccessPathApproxNone() } - - ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } - - import Level1CallContext - import LocalCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc + private predicate nodeMayUseSummary0( + NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, lcc) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) + exists(AccessPathApprox apa0 | + Stage5::parameterMayFlowThrough(p, _) and + Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0) and + Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), + TAccessPathApproxSome(apa), apa0) ) } pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) + private predicate nodeMayUseSummary(NodeEx n, FlowState state, AccessPathApprox apa) { + exists(ParamNodeEx p | + Stage5::parameterMayFlowThrough(p, apa) and + nodeMayUseSummary0(n, p, state, apa) ) } - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { any() } + private newtype TSummaryCtx = + TSummaryCtxNone() or + TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { + Stage5::parameterMayFlowThrough(p, ap.getApprox()) and + Stage5::revFlow(p, state, _) + } - // Type checking is not necessary here as it has already been done in stage 3. - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} + /** + * A context for generating flow summaries. This represents flow entry through + * a specific parameter with an access path of a specific shape. + * + * Summaries are only created for parameters that may flow through. + */ + abstract private class SummaryCtx extends TSummaryCtx { + abstract string toString(); + } -private module Stage5 = MkStage::Stage; + /** A summary context from which no flow summary can be generated. */ + private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { + override string toString() { result = "" } + } -bindingset[conf, result] -private Configuration unbindConf(Configuration conf) { - exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c)) -} + /** A summary context from which a flow summary can be generated. */ + private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { + private ParamNodeEx p; + private FlowState s; + private AccessPath ap; -pragma[nomagic] -private predicate nodeMayUseSummary0( - NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(AccessPathApprox apa0 | - Stage5::parameterMayFlowThrough(p, _, _) and - Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0, config) and - Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), - TAccessPathApproxSome(apa), apa0, config) - ) -} + SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } -pragma[nomagic] -private predicate nodeMayUseSummary( - NodeEx n, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::parameterMayFlowThrough(p, apa, config) and - nodeMayUseSummary0(n, p, state, apa, config) - ) -} + ParameterPosition getParameterPos() { p.isParameterOf(_, result) } -private newtype TSummaryCtx = - TSummaryCtxNone() or - TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { - exists(Configuration config | - Stage5::parameterMayFlowThrough(p, ap.getApprox(), config) and - Stage5::revFlow(p, state, _, config) + ParamNodeEx getParamNode() { result = p } + + override string toString() { result = p + ": " + ap } + + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * Gets the number of length 2 access path approximations that correspond to `apa`. + */ + private int count1to2unfold(AccessPathApproxCons1 apa) { + exists(TypedContent tc, int len | + tc = apa.getHead() and + len = apa.len() and + result = + strictcount(AccessPathFront apf | + Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1)) + ) ) } -/** - * A context for generating flow summaries. This represents flow entry through - * a specific parameter with an access path of a specific shape. - * - * Summaries are only created for parameters that may flow through. - */ -abstract private class SummaryCtx extends TSummaryCtx { - abstract string toString(); -} - -/** A summary context from which no flow summary can be generated. */ -private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { - override string toString() { result = "" } -} - -/** A summary context from which a flow summary can be generated. */ -private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParamNodeEx p; - private FlowState s; - private AccessPath ap; - - SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } - - ParameterPosition getParameterPos() { p.isParameterOf(_, result) } - - ParamNodeEx getParamNode() { result = p } - - override string toString() { result = p + ": " + ap } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** - * Gets the number of length 2 access path approximations that correspond to `apa`. - */ -private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { - exists(TypedContent tc, int len | - tc = apa.getHead() and - len = apa.len() and + private int countNodesUsingAccessPath(AccessPathApprox apa) { result = - strictcount(AccessPathFront apf | - Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), - config) + strictcount(NodeEx n, FlowState state | + Stage5::revFlow(n, state, apa) or nodeMayUseSummary(n, state, apa) ) - ) -} + } -private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = - strictcount(NodeEx n, FlowState state | - Stage5::revFlow(n, state, apa, config) or nodeMayUseSummary(n, state, apa, config) - ) -} - -/** - * Holds if a length 2 access path approximation matching `apa` is expected - * to be expensive. - */ -private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = count1to2unfold(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - apLimit < aps and - tupleLimit < (aps - 1) * nodes - ) -} - -private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { - exists(TypedContent head | - apa.pop(head) = result and - Stage5::consCand(head, result, config) - ) -} - -/** - * Holds with `unfold = false` if a precise head-tail representation of `apa` is - * expected to be expensive. Holds with `unfold = true` otherwise. - */ -private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { - if apa.getHead().forceHighPrecision() - then unfold = true - else + /** + * Holds if a length 2 access path approximation matching `apa` is expected + * to be expensive. + */ + private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa) { exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = countPotentialAps(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and + aps = count1to2unfold(apa) and + nodes = countNodesUsingAccessPath(apa) and accessPathCostLimits(apLimit, tupleLimit) and - if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true - ) -} - -/** - * Gets the number of `AccessPath`s that correspond to `apa`. - */ -pragma[assume_small_delta] -private int countAps(AccessPathApprox apa, Configuration config) { - evalUnfold(apa, false, config) and - result = 1 and - (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) - or - evalUnfold(apa, false, config) and - result = count1to2unfold(apa, config) and - not expensiveLen1to2unfolding(apa, config) - or - evalUnfold(apa, true, config) and - result = countPotentialAps(apa, config) -} - -/** - * Gets the number of `AccessPath`s that would correspond to `apa` assuming - * that it is expanded to a precise head-tail representation. - */ -language[monotonicAggregates] -pragma[assume_small_delta] -private int countPotentialAps(AccessPathApprox apa, Configuration config) { - apa instanceof AccessPathApproxNil and result = 1 - or - result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) -} - -private newtype TAccessPath = - TAccessPathNil(DataFlowType t) or - TAccessPathCons(TypedContent head, AccessPath tail) { - exists(AccessPathApproxCons apa | - not evalUnfold(apa, false, _) and - head = apa.getHead() and - tail.getApprox() = getATail(apa, _) - ) - } or - TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - not expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head1 = apa.getHead() and - head2 = getATail(apa, _).getHead() - ) - } or - TAccessPathCons1(TypedContent head, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head = apa.getHead() + apLimit < aps and + tupleLimit < (aps - 1) * nodes ) } -private newtype TPathNode = - pragma[assume_small_delta] - TPathNodeMid( - NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config - ) { - // A PathNode is introduced by a source ... - Stage5::revFlow(node, state, config) and - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - or - // ... or a step from an existing PathNode to another node. - exists(PathNodeMid mid | - pathStep(mid, node, state, cc, sc, ap) and - pragma[only_bind_into](config) = mid.getConfiguration() and - Stage5::revFlow(node, state, ap.getApprox(), pragma[only_bind_into](config)) - ) - } or - TPathNodeSink(NodeEx node, FlowState state, Configuration config) { - exists(PathNodeMid sink | - sink.isAtSink() and - node = sink.getNodeEx() and - state = sink.getState() and - config = sink.getConfiguration() - ) - } or - TPathNodeSourceGroup(string sourceGroup, Configuration config) { - exists(PathNodeImpl source | - sourceGroup = source.getSourceGroup() and - config = source.getConfiguration() - ) - } or - TPathNodeSinkGroup(string sinkGroup, Configuration config) { - exists(PathNodeSink sink | - sinkGroup = sink.getSinkGroup() and - config = sink.getConfiguration() + private AccessPathApprox getATail(AccessPathApprox apa) { + exists(TypedContent head | + apa.pop(head) = result and + Stage5::consCand(head, result) ) } -/** - * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a - * source to a given node with a given `AccessPath`, this indicates the sequence - * of dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ -private class AccessPath extends TAccessPath { - /** Gets the head of this access path, if any. */ - abstract TypedContent getHead(); - - /** Gets the tail of this access path, if any. */ - abstract AccessPath getTail(); - - /** Gets the front of this access path. */ - abstract AccessPathFront getFront(); - - /** Gets the approximation of this access path. */ - abstract AccessPathApprox getApprox(); - - /** Gets the length of this access path. */ - abstract int length(); - - /** Gets a textual representation of this access path. */ - abstract string toString(); - - /** Gets the access path obtained by popping `tc` from this access path, if any. */ - final AccessPath pop(TypedContent tc) { - result = this.getTail() and - tc = this.getHead() - } - - /** Gets the access path obtained by pushing `tc` onto this access path. */ - final AccessPath push(TypedContent tc) { this = result.pop(tc) } -} - -private class AccessPathNil extends AccessPath, TAccessPathNil { - private DataFlowType t; - - AccessPathNil() { this = TAccessPathNil(t) } - - DataFlowType getType() { result = t } - - override TypedContent getHead() { none() } - - override AccessPath getTail() { none() } - - override AccessPathFrontNil getFront() { result = TFrontNil(t) } - - override AccessPathApproxNil getApprox() { result = TNil(t) } - - override int length() { result = 0 } - - override string toString() { result = concat(": " + ppReprType(t)) } -} - -private class AccessPathCons extends AccessPath, TAccessPathCons { - private TypedContent head; - private AccessPath tail; - - AccessPathCons() { this = TAccessPathCons(head, tail) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { result = tail } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - pragma[assume_small_delta] - override AccessPathApproxCons getApprox() { - result = TConsNil(head, tail.(AccessPathNil).getType()) - or - result = TConsCons(head, tail.getHead(), this.length()) - or - result = TCons1(head, this.length()) - } - - pragma[assume_small_delta] - override int length() { result = 1 + tail.length() } - - private string toStringImpl(boolean needsSuffix) { - exists(DataFlowType t | - tail = TAccessPathNil(t) and - needsSuffix = false and - result = head.toString() + "]" + concat(" : " + ppReprType(t)) - ) - or - result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) - or - exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | - result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true - or - result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false - ) - or - exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | - result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true - or - result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false - ) - } - - override string toString() { - result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" - or - result = "[" + this.toStringImpl(false) - } -} - -private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { - private TypedContent head1; - private TypedContent head2; - private int len; - - AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } - - override TypedContent getHead() { result = head1 } - - override AccessPath getTail() { - Stage5::consCand(head1, result.getApprox(), _) and - result.getHead() = head2 and - result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head1) } - - override AccessPathApproxCons getApprox() { - result = TConsCons(head1, head2, len) or - result = TCons1(head1, len) - } - - override int length() { result = len } - - override string toString() { - if len = 2 - then result = "[" + head1.toString() + ", " + head2.toString() + "]" + /** + * Holds with `unfold = false` if a precise head-tail representation of `apa` is + * expected to be expensive. Holds with `unfold = true` otherwise. + */ + private predicate evalUnfold(AccessPathApprox apa, boolean unfold) { + if apa.getHead().forceHighPrecision() + then unfold = true else - result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" - } -} - -private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { - private TypedContent head; - private int len; - - AccessPathCons1() { this = TAccessPathCons1(head, len) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { - Stage5::consCand(head, result.getApprox(), _) and result.length() = len - 1 + exists(int aps, int nodes, int apLimit, int tupleLimit | + aps = countPotentialAps(apa) and + nodes = countNodesUsingAccessPath(apa) and + accessPathCostLimits(apLimit, tupleLimit) and + if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true + ) } - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - - override int length() { result = len } - - override string toString() { - if len = 1 - then result = "[" + head.toString() + "]" - else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" - } -} - -abstract private class PathNodeImpl extends TPathNode { - /** Gets the `FlowState` of this node. */ - abstract FlowState getState(); - - /** Gets the associated configuration. */ - abstract Configuration getConfiguration(); - - /** Holds if this node is a source. */ - abstract predicate isSource(); - - abstract PathNodeImpl getASuccessorImpl(); - - private PathNodeImpl getASuccessorIfHidden() { - this.isHidden() and - result = this.getASuccessorImpl() - } - - pragma[nomagic] - private PathNodeImpl getANonHiddenSuccessor0() { - result = this.getASuccessorIfHidden*() and - not result.isHidden() - } - - final PathNodeImpl getANonHiddenSuccessor() { - result = this.getASuccessorImpl().getANonHiddenSuccessor0() and - not this.isHidden() - } - - abstract NodeEx getNodeEx(); - - predicate isHidden() { - not this.getConfiguration().includeHiddenNodes() and - ( - hiddenNode(this.getNodeEx().asNode()) and - not this.isSource() and - not this instanceof PathNodeSink - or - this.getNodeEx() instanceof TNodeImplicitRead - ) - } - - string getSourceGroup() { - this.isSource() and - this.getConfiguration().sourceGrouping(this.getNodeEx().asNode(), result) - } - - predicate isFlowSource() { - this.isSource() and not exists(this.getSourceGroup()) + /** + * Gets the number of `AccessPath`s that correspond to `apa`. + */ + pragma[assume_small_delta] + private int countAps(AccessPathApprox apa) { + evalUnfold(apa, false) and + result = 1 and + (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa)) or - this instanceof PathNodeSourceGroup - } - - predicate isFlowSink() { - this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or - this instanceof PathNodeSinkGroup - } - - private string ppAp() { - this instanceof PathNodeSink and result = "" + evalUnfold(apa, false) and + result = count1to2unfold(apa) and + not expensiveLen1to2unfolding(apa) or - exists(string s | s = this.(PathNodeMid).getAp().toString() | - if s = "" then result = "" else result = " " + s - ) + evalUnfold(apa, true) and + result = countPotentialAps(apa) } - private string ppCtx() { - this instanceof PathNodeSink and result = "" + /** + * Gets the number of `AccessPath`s that would correspond to `apa` assuming + * that it is expanded to a precise head-tail representation. + */ + language[monotonicAggregates] + pragma[assume_small_delta] + private int countPotentialAps(AccessPathApprox apa) { + apa instanceof AccessPathApproxNil and result = 1 or - result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + result = strictsum(AccessPathApprox tail | tail = getATail(apa) | countAps(tail)) } - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** Holds if `n` can reach a sink. */ -private predicate directReach(PathNodeImpl n) { - n instanceof PathNodeSink or - n instanceof PathNodeSinkGroup or - directReach(n.getANonHiddenSuccessor()) -} - -/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ -private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } - -/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { - n1.getANonHiddenSuccessor() = n2 and directReach(n2) -} - -private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) - -/** - * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. - * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. - */ -class PathNode instanceof PathNodeImpl { - PathNode() { reach(this) } - - /** Gets a textual representation of this element. */ - final string toString() { result = super.toString() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - final string toStringWithContext() { result = super.toStringWithContext() } - - /** - * 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/). - */ - final predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - /** Gets the underlying `Node`. */ - final Node getNode() { super.getNodeEx().projectToNode() = result } - - /** Gets the `FlowState` of this node. */ - final FlowState getState() { result = super.getState() } - - /** Gets the associated configuration. */ - final Configuration getConfiguration() { result = super.getConfiguration() } - - /** Gets a successor of this node, if any. */ - final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } - - /** Holds if this node is a source. */ - final predicate isSource() { super.isSource() } - - /** Holds if this node is a grouping of source nodes. */ - final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group, _) } - - /** Holds if this node is a grouping of sink nodes. */ - final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group, _) } -} - -/** - * Provides the query predicates needed to include a graph in a path-problem query. - */ -module PathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } - - /** Holds if `n` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode n, string key, string val) { - key = "semmle.label" and val = n.toString() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { - Subpaths::subpaths(arg, par, ret, out) - } -} - -/** - * An intermediate flow graph node. This is a triple consisting of a `Node`, - * a `CallContext`, and a `Configuration`. - */ -private class PathNodeMid extends PathNodeImpl, TPathNodeMid { - NodeEx node; - FlowState state; - CallContext cc; - SummaryCtx sc; - AccessPath ap; - Configuration config; - - PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - SummaryCtx getSummaryCtx() { result = sc } - - AccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - private PathNodeMid getSuccMid() { - pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx(), result.getAp()) and - result.getConfiguration() = unbindConf(this.getConfiguration()) - } - - override PathNodeImpl getASuccessorImpl() { - // an intermediate step to another intermediate node - result = this.getSuccMid() - or - // a final step to a sink - result = this.getSuccMid().projectToSink() - } - - override predicate isSource() { - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - } - - predicate isAtSink() { - sinkNode(node, state, config) and - ap instanceof AccessPathNil and - if hasSinkCallCtx(config) - then - // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` - // is exactly what we need to check. This also implies - // `sc instanceof SummaryCtxNone`. - // For `FeatureEqualSourceSinkCallContext` the initial call context was - // set to `CallContextSomeCall` and jumps are disallowed, so - // `cc instanceof CallContextNoCall` never holds. On the other hand, - // in this case there's never any need to enter a call except to identify - // a summary, so the condition in `pathIntoCallable` enforces this, which - // means that `sc instanceof SummaryCtxNone` holds if and only if we are - // in the call context of the source. - sc instanceof SummaryCtxNone or - cc instanceof CallContextNoCall - else any() - } - - PathNodeSink projectToSink() { - this.isAtSink() and - result.getNodeEx() = node and - result.getState() = state and - result.getConfiguration() = unbindConf(config) - } -} - -/** - * A flow graph node corresponding to a sink. This is disjoint from the - * intermediate nodes in order to uniquely correspond to a given sink by - * excluding the `CallContext`. - */ -private class PathNodeSink extends PathNodeImpl, TPathNodeSink { - NodeEx node; - FlowState state; - Configuration config; - - PathNodeSink() { this = TPathNodeSink(node, state, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result = TPathNodeSinkGroup(this.getSinkGroup(), config) - } - - override predicate isSource() { sourceNode(node, state, config) } - - string getSinkGroup() { config.sinkGrouping(node.asNode(), result) } -} - -private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { - string sourceGroup; - Configuration config; - - PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result.getSourceGroup() = sourceGroup and - result.getConfiguration() = config - } - - override predicate isSource() { none() } - - override string toString() { result = sourceGroup } - - override 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 - } -} - -private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { - string sinkGroup; - Configuration config; - - PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { none() } - - override predicate isSource() { none() } - - override string toString() { result = sinkGroup } - - override 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 - } -} - -private predicate pathNode( - PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, - Configuration conf, LocalCallContext localCC -) { - midnode = mid.getNodeEx() and - state = mid.getState() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - midnode.getEnclosingCallable()) and - ap = mid.getAp() -} - -/** - * Holds if data may flow from `mid` to `node`. The last step in or out of - * a callable is recorded by `cc`. - */ -pragma[assume_small_delta] -pragma[nomagic] -private predicate pathStep( - PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap -) { - exists(NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC | - pathNode(mid, midnode, state0, cc, sc, ap, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, true, _, conf, localCC) - ) - or - exists( - AccessPath ap0, NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC - | - pathNode(mid, midnode, state0, cc, sc, ap0, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), conf, - localCC) and - ap0 instanceof AccessPathNil - ) - or - jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - ap = mid.getAp() - or - additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, mid.getConfiguration()) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - pathIntoCallable(mid, node, state, _, cc, sc, _, _) and ap = mid.getAp() - or - pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone - or - pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() -} - -pragma[nomagic] -private predicate pathReadStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - tc = ap0.getHead() and - Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -pragma[nomagic] -private predicate pathStoreStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -private predicate pathOutOfCallable0( - PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa, - Configuration config -) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - apa = mid.getAp().getApprox() and - config = mid.getConfiguration() -} - -pragma[nomagic] -private predicate pathOutOfCallable1( - PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPathApprox apa, Configuration config -) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - pathOutOfCallable0(mid, pos, state, innercc, apa, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) -} - -pragma[noinline] -private NodeEx getAnOutNodeFlow( - ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config -) { - result.asNode() = kind.getAnOutNode(call) and - Stage5::revFlow(result, _, apa, config) -} - -/** - * Holds if data may flow from `mid` to `out`. The last step of this path - * is a return from a callable and is recorded by `cc`, if needed. - */ -pragma[noinline] -private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { - exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | - pathOutOfCallable1(mid, call, kind, state, cc, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. - */ -pragma[noinline] -private predicate pathIntoArg( - PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(ArgNodeEx arg, ArgumentPosition apos | - pathNode(mid, arg, state, cc, _, ap, config, _) and - arg.asNode().(ArgNode).argumentOf(call, apos) and - apa = ap.getApprox() and - parameterMatch(ppos, apos) - ) -} - -pragma[nomagic] -private predicate parameterCand( - DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::revFlow(p, _, apa, config) and - p.isParameterOf(callable, pos) - ) -} - -pragma[nomagic] -private predicate pathIntoCallable0( - PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, AccessPath ap, Configuration config -) { - exists(AccessPathApprox apa | - pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - callable = resolveCall(call, outercc) and - parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa), - pragma[only_bind_into](config)) - ) -} - -/** - * Holds if data may flow from `mid` to `p` through `call`. The contexts - * before and after entering the callable are `outercc` and `innercc`, - * respectively. - */ -pragma[nomagic] -private predicate pathIntoCallable( - PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, - SummaryCtx sc, DataFlowCall call, Configuration config -) { - exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | - pathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - ( - sc = TSummaryCtxSome(p, state, ap) - or - not exists(TSummaryCtxSome(p, state, ap)) and - sc = TSummaryCtxNone() and - // When the call contexts of source and sink needs to match then there's - // never any reason to enter a callable except to find a summary. See also - // the comment in `PathNodeMid::isAtSink`. - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) -} - -/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ -pragma[nomagic] -private predicate paramFlowsThrough( - ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, - AccessPathApprox apa, Configuration config -) { - exists(RetNodeEx ret | - pathNode(_, ret, state, cc, sc, ap, config, _) and - kind = ret.getKind() and - apa = ap.getApprox() and - parameterFlowThroughAllowed(sc.getParamNode(), kind) - ) -} - -pragma[nomagic] -private predicate pathThroughCallable0( - DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(CallContext innercc, SummaryCtx sc | - pathIntoCallable(mid, _, _, cc, innercc, sc, call, config) and - paramFlowsThrough(kind, state, innercc, sc, ap, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` through a callable to the node `out`. - * The context `cc` is restored to its value prior to entering the callable. - */ -pragma[noinline] -private predicate pathThroughCallable( - PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap -) { - exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config | - pathThroughCallable0(call, mid, kind, state, cc, ap, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -private module Subpaths { - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths01( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - exists(Configuration config | - pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and - pathIntoCallable(arg, par, _, _, innercc, sc, _, config) and - paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, - pragma[only_bind_into](apout), _, unbindConf(config)) and - not arg.isHidden() - ) - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `sout`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths02( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and - out.asNode() = kind.getAnOutNode(_) - } - - pragma[nomagic] - private Configuration getPathNodeConf(PathNodeImpl n) { result = n.getConfiguration() } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple. - */ - pragma[nomagic] - private predicate subpaths03( - PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, AccessPath apout - ) { - exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | - subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and - pathNode(ret, retnode, sout, innercc, sc, apout, unbindConf(getPathNodeConf(arg)), _) and - kind = retnode.getKind() - ) - } - - private PathNodeImpl localStepToHidden(PathNodeImpl n) { - n.getASuccessorImpl() = result and - result.isHidden() and - exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | - localFlowBigStep(n1, _, n2, _, _, _, _, _) or - store(n1, _, n2, _, _) or - readSet(n1, _, n2, _) - ) - } - - pragma[nomagic] - private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { - succ = pred.getANonHiddenSuccessor() and - succNode = succ.getNodeEx() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { - exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | - pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and - subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and - hasSuccessor(pragma[only_bind_into](arg), par, p) and - not ret.isHidden() and - pathNode(out0, o, sout, _, _, apout, _, _) - | - out = out0 or out = out0.projectToSink() - ) - } - - /** - * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. - */ - predicate retReach(PathNodeImpl n) { - exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) - or - exists(PathNodeImpl mid | - retReach(mid) and - n.getANonHiddenSuccessor() = mid and - not subpaths(_, mid, _, _) - ) - } -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -private predicate hasFlowPath( - PathNodeImpl flowsource, PathNodeImpl flowsink, Configuration configuration -) { - flowsource.isFlowSource() and - flowsource.getConfiguration() = configuration and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.isFlowSink() -} - -private predicate flowsTo( - PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink, - Configuration configuration -) { - flowsource.isSource() and - flowsource.getConfiguration() = configuration and - flowsource.getNodeEx().asNode() = source and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.getNodeEx().asNode() = sink -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -predicate flowsTo(Node source, Node sink, Configuration configuration) { - flowsTo(_, _, source, sink, configuration) -} - -private predicate finalStats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples -) { - fwd = true and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and - tuples = count(PathNodeImpl pn) - or - fwd = false and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and - tuples = count(PathNode pn) -} - -/** - * INTERNAL: Only for debugging. - * - * Calculates per-stage metrics for data flow. - */ -predicate stageStats( - int n, string stage, int nodes, int fields, int conscand, int states, int tuples, - Configuration config -) { - stage = "1 Fwd" and - n = 10 and - Stage1::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "1 Rev" and - n = 15 and - Stage1::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Fwd" and - n = 20 and - Stage2::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Rev" and - n = 25 and - Stage2::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Fwd" and - n = 30 and - Stage3::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Rev" and - n = 35 and - Stage3::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Fwd" and - n = 40 and - Stage4::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Rev" and - n = 45 and - Stage4::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Fwd" and - n = 50 and - Stage5::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Rev" and - n = 55 and - Stage5::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) - or - stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) -} - -private module FlowExploration { - private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - jumpStep(node1, node2, config) - or - additionalJumpStep(node1, node2, config) - or - additionalJumpStateStep(node1, _, node2, _, config) - or - // flow into callable - viableParamArgEx(_, node2, node1) - or - // flow out of a callable - viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) - | - c1 = node1.getEnclosingCallable() and - c2 = node2.getEnclosingCallable() and - c1 != c2 - ) - } - - private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSource(n) or config.isSource(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSrc(mid, config) and callableStep(mid, c, config) - ) - } - - private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSink(n) or config.isSink(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSink(mid, config) and callableStep(c, mid, config) - ) - } - - private newtype TCallableExt = - TCallable(DataFlowCallable c, Configuration config) { - interestingCallableSrc(c, config) or - interestingCallableSink(c, config) + private newtype TAccessPath = + TAccessPathNil(DataFlowType t) or + TAccessPathCons(TypedContent head, AccessPath tail) { + exists(AccessPathApproxCons apa | + not evalUnfold(apa, false) and + head = apa.getHead() and + tail.getApprox() = getATail(apa) + ) } or - TCallableSrc() or - TCallableSink() + TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { + exists(AccessPathApproxCons apa | + evalUnfold(apa, false) and + not expensiveLen1to2unfolding(apa) and + apa.len() = len and + head1 = apa.getHead() and + head2 = getATail(apa).getHead() + ) + } or + TAccessPathCons1(TypedContent head, int len) { + exists(AccessPathApproxCons apa | + evalUnfold(apa, false) and + expensiveLen1to2unfolding(apa) and + apa.len() = len and + head = apa.getHead() + ) + } - private predicate callableExtSrc(TCallableSrc src) { any() } - - private predicate callableExtSink(TCallableSink sink) { any() } - - private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { - exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | - callableStep(c1, c2, config) and - ce1 = TCallable(c1, pragma[only_bind_into](config)) and - ce2 = TCallable(c2, pragma[only_bind_into](config)) - ) - or - exists(Node n, Configuration config | - ce1 = TCallableSrc() and - (config.isSource(n) or config.isSource(n, _)) and - ce2 = TCallable(getNodeEnclosingCallable(n), config) - ) - or - exists(Node n, Configuration config | - ce2 = TCallableSink() and - (config.isSink(n) or config.isSink(n, _)) and - ce1 = TCallable(getNodeEnclosingCallable(n), config) - ) - } - - private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { - callableExtStepFwd(ce2, ce1) - } - - private int distSrcExt(TCallableExt c) = - shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) - - private int distSinkExt(TCallableExt c) = - shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) - - private int distSrc(DataFlowCallable c, Configuration config) { - result = distSrcExt(TCallable(c, config)) - 1 - } - - private int distSink(DataFlowCallable c, Configuration config) { - result = distSinkExt(TCallable(c, config)) - 1 - } - - private newtype TPartialAccessPath = - TPartialNil(DataFlowType t) or - TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } + private newtype TPathNode = + pragma[assume_small_delta] + TPathNodeMid(NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap) { + // A PathNode is introduced by a source ... + Stage5::revFlow(node, state) and + sourceNode(node, state) and + ( + if hasSourceCallCtx() + then cc instanceof CallContextSomeCall + else cc instanceof CallContextAny + ) and + sc instanceof SummaryCtxNone and + ap = TAccessPathNil(node.getDataFlowType()) + or + // ... or a step from an existing PathNode to another node. + pathStep(_, node, state, cc, sc, ap) and + Stage5::revFlow(node, state, ap.getApprox()) + } or + TPathNodeSink(NodeEx node, FlowState state) { + exists(PathNodeMid sink | + sink.isAtSink() and + node = sink.getNodeEx() and + state = sink.getState() + ) + } or + TPathNodeSourceGroup(string sourceGroup) { + exists(PathNodeImpl source | sourceGroup = source.getSourceGroup()) + } or + TPathNodeSinkGroup(string sinkGroup) { + exists(PathNodeSink sink | sinkGroup = sink.getSinkGroup()) + } /** - * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first - * element of the list and its length are tracked. If data flows from a source to - * a given node with a given `AccessPath`, this indicates the sequence of - * dereference operations needed to get from the value in the node to the + * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a + * source to a given node with a given `AccessPath`, this indicates the sequence + * of dereference operations needed to get from the value in the node to the * tracked object. The final type indicates the type of the tracked object. */ - private class PartialAccessPath extends TPartialAccessPath { + private class AccessPath extends TAccessPath { + /** Gets the head of this access path, if any. */ + abstract TypedContent getHead(); + + /** Gets the tail of this access path, if any. */ + abstract AccessPath getTail(); + + /** Gets the front of this access path. */ + abstract AccessPathFront getFront(); + + /** Gets the approximation of this access path. */ + abstract AccessPathApprox getApprox(); + + /** Gets the length of this access path. */ + abstract int length(); + + /** Gets a textual representation of this access path. */ abstract string toString(); - TypedContent getHead() { this = TPartialCons(result, _) } - - int len() { - this = TPartialNil(_) and result = 0 - or - this = TPartialCons(_, result) + /** Gets the access path obtained by popping `tc` from this access path, if any. */ + final AccessPath pop(TypedContent tc) { + result = this.getTail() and + tc = this.getHead() } - DataFlowType getType() { - this = TPartialNil(result) - or - exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) - } + /** Gets the access path obtained by pushing `tc` onto this access path. */ + final AccessPath push(TypedContent tc) { this = result.pop(tc) } } - private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { - override string toString() { - exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) - } + private class AccessPathNil extends AccessPath, TAccessPathNil { + private DataFlowType t; + + AccessPathNil() { this = TAccessPathNil(t) } + + DataFlowType getType() { result = t } + + override TypedContent getHead() { none() } + + override AccessPath getTail() { none() } + + override AccessPathFrontNil getFront() { result = TFrontNil(t) } + + override AccessPathApproxNil getApprox() { result = TNil(t) } + + override int length() { result = 0 } + + override string toString() { result = concat(": " + ppReprType(t)) } } - private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { - override string toString() { - exists(TypedContent tc, int len | this = TPartialCons(tc, len) | - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" + private class AccessPathCons extends AccessPath, TAccessPathCons { + private TypedContent head; + private AccessPath tail; + + AccessPathCons() { this = TAccessPathCons(head, tail) } + + override TypedContent getHead() { result = head } + + override AccessPath getTail() { result = tail } + + override AccessPathFrontHead getFront() { result = TFrontHead(head) } + + pragma[assume_small_delta] + override AccessPathApproxCons getApprox() { + result = TConsNil(head, tail.(AccessPathNil).getType()) + or + result = TConsCons(head, tail.getHead(), this.length()) + or + result = TCons1(head, this.length()) + } + + pragma[assume_small_delta] + override int length() { result = 1 + tail.length() } + + private string toStringImpl(boolean needsSuffix) { + exists(DataFlowType t | + tail = TAccessPathNil(t) and + needsSuffix = false and + result = head.toString() + "]" + concat(" : " + ppReprType(t)) + ) + or + result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) + or + exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | + result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true + or + result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false + ) + or + exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | + result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true + or + result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false ) } - } - private newtype TRevPartialAccessPath = - TRevPartialNil() or - TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `Content`s, but only the first - * element of the list and its length are tracked. - */ - private class RevPartialAccessPath extends TRevPartialAccessPath { - abstract string toString(); - - Content getHead() { this = TRevPartialCons(result, _) } - - int len() { - this = TRevPartialNil() and result = 0 - or - this = TRevPartialCons(_, result) - } - } - - private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { - override string toString() { result = "" } - } - - private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { override string toString() { - exists(Content c, int len | this = TRevPartialCons(c, len) | - if len = 1 - then result = "[" + c.toString() + "]" - else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" - ) + result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" + or + result = "[" + this.toStringImpl(false) } } - private predicate relevantState(FlowState state) { - sourceNode(_, state, _) or - sinkNode(_, state, _) or - additionalLocalStateStep(_, state, _, _, _) or - additionalLocalStateStep(_, _, _, state, _) or - additionalJumpStateStep(_, state, _, _, _) or - additionalJumpStateStep(_, _, _, state, _) + private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { + private TypedContent head1; + private TypedContent head2; + private int len; + + AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } + + override TypedContent getHead() { result = head1 } + + override AccessPath getTail() { + Stage5::consCand(head1, result.getApprox()) and + result.getHead() = head2 and + result.length() = len - 1 + } + + override AccessPathFrontHead getFront() { result = TFrontHead(head1) } + + override AccessPathApproxCons getApprox() { + result = TConsCons(head1, head2, len) or + result = TCons1(head1, len) + } + + override int length() { result = len } + + override string toString() { + if len = 2 + then result = "[" + head1.toString() + ", " + head2.toString() + "]" + else + result = + "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" + } } - private newtype TSummaryCtx1 = - TSummaryCtx1None() or - TSummaryCtx1Param(ParamNodeEx p) + private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { + private TypedContent head; + private int len; - private newtype TSummaryCtx2 = - TSummaryCtx2None() or - TSummaryCtx2Some(FlowState s) { relevantState(s) } + AccessPathCons1() { this = TAccessPathCons1(head, len) } - private newtype TSummaryCtx3 = - TSummaryCtx3None() or - TSummaryCtx3Some(PartialAccessPath ap) + override TypedContent getHead() { result = head } - private newtype TRevSummaryCtx1 = - TRevSummaryCtx1None() or - TRevSummaryCtx1Some(ReturnPosition pos) + override AccessPath getTail() { + Stage5::consCand(head, result.getApprox()) and result.length() = len - 1 + } - private newtype TRevSummaryCtx2 = - TRevSummaryCtx2None() or - TRevSummaryCtx2Some(FlowState s) { relevantState(s) } + override AccessPathFrontHead getFront() { result = TFrontHead(head) } - private newtype TRevSummaryCtx3 = - TRevSummaryCtx3None() or - TRevSummaryCtx3Some(RevPartialAccessPath ap) + override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - private newtype TPartialPathNode = - TPartialPathNodeFwd( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = TPartialNil(node.getDataFlowType()) and - exists(config.explorationLimit()) - or - partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap, config) and - distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() - } or - TPartialPathNodeRev( - NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, - RevPartialAccessPath ap, Configuration config - ) { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() and - exists(config.explorationLimit()) - or - revPartialPathStep(_, node, state, sc1, sc2, sc3, ap, config) and - not clearsContentEx(node, ap.getHead()) and + override int length() { result = len } + + override string toString() { + if len = 1 + then result = "[" + head.toString() + "]" + else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" + } + } + + abstract private class PathNodeImpl extends TPathNode { + /** Gets the `FlowState` of this node. */ + abstract FlowState getState(); + + /** Holds if this node is a source. */ + abstract predicate isSource(); + + abstract PathNodeImpl getASuccessorImpl(); + + private PathNodeImpl getASuccessorIfHidden() { + this.isHidden() and + result = this.getASuccessorImpl() + } + + pragma[nomagic] + private PathNodeImpl getANonHiddenSuccessor0() { + result = this.getASuccessorIfHidden*() and + not result.isHidden() + } + + final PathNodeImpl getANonHiddenSuccessor() { + result = this.getASuccessorImpl().getANonHiddenSuccessor0() and + not this.isHidden() + } + + abstract NodeEx getNodeEx(); + + predicate isHidden() { + not Config::includeHiddenNodes() and ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead()) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() + hiddenNode(this.getNodeEx().asNode()) and + not this.isSource() and + not this instanceof PathNodeSink + or + this.getNodeEx() instanceof TNodeImplicitRead + ) } - pragma[nomagic] - private predicate partialPathNodeMk0( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap, config) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - not clearsContentEx(node, ap.getHead().getContent()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead().getContent()) - ) and - if node.asNode() instanceof CastingNode - then compatibleTypes(node.getDataFlowType(), ap.getType()) - else any() - } + string getSourceGroup() { + this.isSource() and + Config::sourceGrouping(this.getNodeEx().asNode(), result) + } + + predicate isFlowSource() { + this.isSource() and not exists(this.getSourceGroup()) + or + this instanceof PathNodeSourceGroup + } + + predicate isFlowSink() { + this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or + this instanceof PathNodeSinkGroup + } + + private string ppAp() { + this instanceof PathNodeSink and result = "" + or + exists(string s | s = this.(PathNodeMid).getAp().toString() | + if s = "" then result = "" else result = " " + s + ) + } + + private string ppCtx() { + this instanceof PathNodeSink and result = "" + or + result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + } - /** - * A `Node` augmented with a call context, an access path, and a configuration. - */ - class PartialPathNode extends TPartialPathNode { /** Gets a textual representation of this element. */ string toString() { result = this.getNodeEx().toString() + this.ppAp() } @@ -4341,305 +3073,339 @@ private module FlowExploration { ) { this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) } + } + + /** Holds if `n` can reach a sink. */ + private predicate directReach(PathNodeImpl n) { + n instanceof PathNodeSink or + n instanceof PathNodeSinkGroup or + directReach(n.getANonHiddenSuccessor()) + } + + /** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ + private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } + + /** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ + private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { + n1.getANonHiddenSuccessor() = n2 and directReach(n2) + } + + private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) + + /** + * A `Node` augmented with a call context (except for sinks) and an access path. + * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. + */ + class PathNode instanceof PathNodeImpl { + PathNode() { reach(this) } + + /** Gets a textual representation of this element. */ + final string toString() { result = super.toString() } + + /** + * Gets a textual representation of this element, including a textual + * representation of the call context. + */ + final string toStringWithContext() { result = super.toStringWithContext() } + + /** + * 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/). + */ + final predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } /** Gets the underlying `Node`. */ - final Node getNode() { this.getNodeEx().projectToNode() = result } + final Node getNode() { super.getNodeEx().projectToNode() = result } - FlowState getState() { none() } - - private NodeEx getNodeEx() { - result = this.(PartialPathNodeFwd).getNodeEx() or - result = this.(PartialPathNodeRev).getNodeEx() - } - - /** Gets the associated configuration. */ - Configuration getConfiguration() { none() } + /** Gets the `FlowState` of this node. */ + final FlowState getState() { result = super.getState() } /** Gets a successor of this node, if any. */ - PartialPathNode getASuccessor() { none() } + final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } - /** - * Gets the approximate distance to the nearest source measured in number - * of interprocedural steps. - */ - int getSourceDistance() { - result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } + /** Holds if this node is a source. */ + final predicate isSource() { super.isSource() } - /** - * Gets the approximate distance to the nearest sink measured in number - * of interprocedural steps. - */ - int getSinkDistance() { - result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } + /** Holds if this node is a grouping of source nodes. */ + final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group) } - private string ppAp() { - exists(string s | - s = this.(PartialPathNodeFwd).getAp().toString() or - s = this.(PartialPathNodeRev).getAp().toString() - | - if s = "" then result = "" else result = " " + s - ) - } - - private string ppCtx() { - result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" - } - - /** Holds if this is a source in a forward-flow path. */ - predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } - - /** Holds if this is a sink in a reverse-flow path. */ - predicate isRevSink() { this.(PartialPathNodeRev).isSink() } + /** Holds if this node is a grouping of sink nodes. */ + final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group) } } /** * Provides the query predicates needed to include a graph in a path-problem query. */ - module PartialPathGraph { + module PathGraph { /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } + query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } + + /** Holds if `n` is a node in the graph of data flow path explanations. */ + query predicate nodes(PathNode n, string key, string val) { + key = "semmle.label" and val = n.toString() + } + + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through + * a subpath between `par` and `ret` with the connecting edges `arg -> par` and + * `ret -> out` is summarized as the edge `arg -> out`. + */ + query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { + Subpaths::subpaths(arg, par, ret, out) + } } - private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { + /** + * An intermediate flow graph node. This is a tuple consisting of a `Node`, + * a `FlowState`, a `CallContext`, a `SummaryCtx`, and an `AccessPath`. + */ + private class PathNodeMid extends PathNodeImpl, TPathNodeMid { NodeEx node; FlowState state; CallContext cc; - TSummaryCtx1 sc1; - TSummaryCtx2 sc2; - TSummaryCtx3 sc3; - PartialAccessPath ap; - Configuration config; + SummaryCtx sc; + AccessPath ap; - PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap, config) } + PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap) } - NodeEx getNodeEx() { result = node } + override NodeEx getNodeEx() { result = node } override FlowState getState() { result = state } CallContext getCallContext() { result = cc } - TSummaryCtx1 getSummaryCtx1() { result = sc1 } + SummaryCtx getSummaryCtx() { result = sc } - TSummaryCtx2 getSummaryCtx2() { result = sc2 } + AccessPath getAp() { result = ap } - TSummaryCtx3 getSummaryCtx3() { result = sc3 } - - PartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeFwd getASuccessor() { - partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp(), - result.getConfiguration()) + private PathNodeMid getSuccMid() { + pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), + result.getSummaryCtx(), result.getAp()) } - predicate isSource() { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap instanceof TPartialNil + override PathNodeImpl getASuccessorImpl() { + // an intermediate step to another intermediate node + result = this.getSuccMid() + or + // a final step to a sink + result = this.getSuccMid().projectToSink() + } + + override predicate isSource() { + sourceNode(node, state) and + ( + if hasSourceCallCtx() + then cc instanceof CallContextSomeCall + else cc instanceof CallContextAny + ) and + sc instanceof SummaryCtxNone and + ap = TAccessPathNil(node.getDataFlowType()) + } + + predicate isAtSink() { + sinkNode(node, state) and + ap instanceof AccessPathNil and + if hasSinkCallCtx() + then + // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` + // is exactly what we need to check. This also implies + // `sc instanceof SummaryCtxNone`. + // For `FeatureEqualSourceSinkCallContext` the initial call context was + // set to `CallContextSomeCall` and jumps are disallowed, so + // `cc instanceof CallContextNoCall` never holds. On the other hand, + // in this case there's never any need to enter a call except to identify + // a summary, so the condition in `pathIntoCallable` enforces this, which + // means that `sc instanceof SummaryCtxNone` holds if and only if we are + // in the call context of the source. + sc instanceof SummaryCtxNone or + cc instanceof CallContextNoCall + else any() + } + + PathNodeSink projectToSink() { + this.isAtSink() and + result.getNodeEx() = node and + result.getState() = state } } - private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { + /** + * A flow graph node corresponding to a sink. This is disjoint from the + * intermediate nodes in order to uniquely correspond to a given sink by + * excluding the `CallContext`. + */ + private class PathNodeSink extends PathNodeImpl, TPathNodeSink { NodeEx node; FlowState state; - TRevSummaryCtx1 sc1; - TRevSummaryCtx2 sc2; - TRevSummaryCtx3 sc3; - RevPartialAccessPath ap; - Configuration config; - PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap, config) } + PathNodeSink() { this = TPathNodeSink(node, state) } - NodeEx getNodeEx() { result = node } + override NodeEx getNodeEx() { result = node } override FlowState getState() { result = state } - TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } + override PathNodeImpl getASuccessorImpl() { result = TPathNodeSinkGroup(this.getSinkGroup()) } - TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } + override predicate isSource() { sourceNode(node, state) } - TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } + string getSinkGroup() { Config::sinkGrouping(node.asNode(), result) } + } - RevPartialAccessPath getAp() { result = ap } + private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { + string sourceGroup; - override Configuration getConfiguration() { result = config } + PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup) } - override PartialPathNodeRev getASuccessor() { - revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), - this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp(), this.getConfiguration()) - } + override NodeEx getNodeEx() { none() } - predicate isSink() { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() + override FlowState getState() { none() } + + override PathNodeImpl getASuccessorImpl() { result.getSourceGroup() = sourceGroup } + + override predicate isSource() { none() } + + override string toString() { result = sourceGroup } + + override 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 } } - private predicate partialPathStep( - PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, - TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config + private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { + string sinkGroup; + + PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup) } + + override NodeEx getNodeEx() { none() } + + override FlowState getState() { none() } + + override PathNodeImpl getASuccessorImpl() { none() } + + override predicate isSource() { none() } + + override string toString() { result = sinkGroup } + + override 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 + } + } + + private predicate pathNode( + PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, + LocalCallContext localCC ) { - not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and - ( - localFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - ) - or - jumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - partialPathStoreStep(mid, _, _, node, ap) and + midnode = mid.getNodeEx() and state = mid.getState() and cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(PartialAccessPath ap0, TypedContent tc | - partialPathReadStep(mid, ap0, tc, node, cc, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsFwd(ap, tc, ap0, config) - ) - or - partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap, config) - or - partialPathOutOfCallable(mid, node, state, cc, ap, config) and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() - or - partialPathThroughCallable(mid, node, state, cc, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() + sc = mid.getSummaryCtx() and + localCC = + getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), + midnode.getEnclosingCallable()) and + ap = mid.getAp() } - bindingset[result, i] - private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } - - pragma[inline] - private predicate partialPathStoreStep( - PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, - PartialAccessPath ap2 + /** + * Holds if data may flow from `mid` to `node`. The last step in or out of + * a callable is recorded by `cc`. + */ + pragma[assume_small_delta] + pragma[nomagic] + private predicate pathStep( + PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap ) { - exists(NodeEx midNode, DataFlowType contentType | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - store(midNode, tc, node, contentType, mid.getConfiguration()) and - ap2.getHead() = tc and - ap2.len() = unbindInt(ap1.len() + 1) and - compatibleTypes(ap1.getType(), contentType) + exists(NodeEx midnode, FlowState state0, LocalCallContext localCC | + pathNode(mid, midnode, state0, cc, sc, ap, localCC) and + localFlowBigStep(midnode, state0, node, state, true, _, localCC) ) + or + exists(AccessPath ap0, NodeEx midnode, FlowState state0, LocalCallContext localCC | + pathNode(mid, midnode, state0, cc, sc, ap0, localCC) and + localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), localCC) and + ap0 instanceof AccessPathNil + ) + or + jumpStepEx(mid.getNodeEx(), node) and + state = mid.getState() and + cc instanceof CallContextAny and + sc instanceof SummaryCtxNone and + ap = mid.getAp() + or + additionalJumpStep(mid.getNodeEx(), node) and + state = mid.getState() and + cc instanceof CallContextAny and + sc instanceof SummaryCtxNone and + mid.getAp() instanceof AccessPathNil and + ap = TAccessPathNil(node.getDataFlowType()) + or + additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state) and + cc instanceof CallContextAny and + sc instanceof SummaryCtxNone and + mid.getAp() instanceof AccessPathNil and + ap = TAccessPathNil(node.getDataFlowType()) + or + exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and + sc = mid.getSummaryCtx() + or + exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and + sc = mid.getSummaryCtx() + or + pathIntoCallable(mid, node, state, _, cc, sc, _) and ap = mid.getAp() + or + pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone + or + pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() } pragma[nomagic] - private predicate apConsFwd( - PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config + private predicate pathReadStep( + PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc ) { - exists(PartialPathNodeFwd mid | - partialPathStoreStep(mid, ap1, tc, _, ap2) and - config = mid.getConfiguration() - ) + ap0 = mid.getAp() and + tc = ap0.getHead() and + Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node) and + state = mid.getState() and + cc = mid.getCallContext() } pragma[nomagic] - private predicate partialPathReadStep( - PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc, - Configuration config + private predicate pathStoreStep( + PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and - ap.getHead() = tc and - pragma[only_bind_into](config) = mid.getConfiguration() and - cc = mid.getCallContext() - ) + ap0 = mid.getAp() and + Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _) and + state = mid.getState() and + cc = mid.getCallContext() } - private predicate partialPathOutOfCallable0( - PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, - PartialAccessPath ap, Configuration config + private predicate pathOutOfCallable0( + PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa ) { pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and state = mid.getState() and innercc = mid.getCallContext() and innercc instanceof CallContextNoCall and - ap = mid.getAp() and - config = mid.getConfiguration() + apa = mid.getAp().getApprox() } pragma[nomagic] - private predicate partialPathOutOfCallable1( - PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config + private predicate pathOutOfCallable1( + PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, + AccessPathApprox apa ) { exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - partialPathOutOfCallable0(mid, pos, state, innercc, ap, config) and + pathOutOfCallable0(mid, pos, state, innercc, apa) and c = pos.getCallable() and kind = pos.getKind() and resolveReturn(innercc, c, call) @@ -4648,53 +3414,86 @@ private module FlowExploration { ) } - private predicate partialPathOutOfCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(ReturnKindExt kind, DataFlowCall call | - partialPathOutOfCallable1(mid, call, kind, state, cc, ap, config) - | - out.asNode() = kind.getAnOutNode(call) + pragma[noinline] + private NodeEx getAnOutNodeFlow(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa) { + result.asNode() = kind.getAnOutNode(call) and + Stage5::revFlow(result, _, apa) + } + + /** + * Holds if data may flow from `mid` to `out`. The last step of this path + * is a return from a callable and is recorded by `cc`, if needed. + */ + pragma[noinline] + private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { + exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa | + pathOutOfCallable1(mid, call, kind, state, cc, apa) and + out = getAnOutNodeFlow(kind, call, apa) ) } + /** + * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. + */ pragma[noinline] - private predicate partialPathIntoArg( - PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, - DataFlowCall call, PartialAccessPath ap, Configuration config + private predicate pathIntoArg( + PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, + AccessPath ap, AccessPathApprox apa ) { - exists(ArgNode arg, ArgumentPosition apos | - arg = mid.getNodeEx().asNode() and - state = mid.getState() and - cc = mid.getCallContext() and - arg.argumentOf(call, apos) and - ap = mid.getAp() and - config = mid.getConfiguration() and + exists(ArgNodeEx arg, ArgumentPosition apos | + pathNode(mid, arg, state, cc, _, ap, _) and + arg.asNode().(ArgNode).argumentOf(call, apos) and + apa = ap.getApprox() and parameterMatch(ppos, apos) ) } pragma[nomagic] - private predicate partialPathIntoCallable0( - PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, PartialAccessPath ap, Configuration config + private predicate parameterCand( + DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa ) { - partialPathIntoArg(mid, pos, state, outercc, call, ap, config) and - callable = resolveCall(call, outercc) + exists(ParamNodeEx p | + Stage5::revFlow(p, _, apa) and + p.isParameterOf(callable, pos) + ) } - private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, - CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, - DataFlowCall call, PartialAccessPath ap, Configuration config + pragma[nomagic] + private predicate pathIntoCallable0( + PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, + CallContext outercc, DataFlowCall call, AccessPath ap ) { - exists(ParameterPosition pos, DataFlowCallable callable | - partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and + exists(AccessPathApprox apa | + pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, + pragma[only_bind_into](apa)) and + callable = resolveCall(call, outercc) and + parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa)) + ) + } + + /** + * Holds if data may flow from `mid` to `p` through `call`. The contexts + * before and after entering the callable are `outercc` and `innercc`, + * respectively. + */ + pragma[nomagic] + private predicate pathIntoCallable( + PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, + SummaryCtx sc, DataFlowCall call + ) { + exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | + pathIntoCallable0(mid, callable, pos, state, outercc, call, ap) and p.isParameterOf(callable, pos) and - sc1 = TSummaryCtx1Param(p) and - sc2 = TSummaryCtx2Some(state) and - sc3 = TSummaryCtx3Some(ap) + ( + sc = TSummaryCtxSome(p, state, ap) + or + not exists(TSummaryCtxSome(p, state, ap)) and + sc = TSummaryCtxNone() and + // When the call contexts of source and sink needs to match then there's + // never any reason to enter a callable except to find a summary. See also + // the comment in `PathNodeMid::isAtSink`. + not Config::getAFeature() instanceof FeatureEqualSourceSinkCallContext + ) | if recordDataFlowCallSite(call, callable) then innercc = TSpecificCall(call) @@ -4702,252 +3501,1108 @@ private module FlowExploration { ) } + /** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ pragma[nomagic] - private predicate paramFlowsThroughInPartialPath( - ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config + private predicate paramFlowsThrough( + ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, + AccessPathApprox apa ) { - exists(PartialPathNodeFwd mid, RetNodeEx ret | - mid.getNodeEx() = ret and + exists(RetNodeEx ret | + pathNode(_, ret, state, cc, sc, ap, _) and kind = ret.getKind() and + apa = ap.getApprox() and + parameterFlowThroughAllowed(sc.getParamNode(), kind) + ) + } + + pragma[assume_small_delta] + pragma[nomagic] + private predicate pathThroughCallable0( + DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, + AccessPath ap, AccessPathApprox apa + ) { + exists(CallContext innercc, SummaryCtx sc | + pathIntoCallable(mid, _, _, cc, innercc, sc, call) and + paramFlowsThrough(kind, state, innercc, sc, ap, apa) + ) + } + + /** + * Holds if data may flow from `mid` through a callable to the node `out`. + * The context `cc` is restored to its value prior to entering the callable. + */ + pragma[noinline] + private predicate pathThroughCallable( + PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap + ) { + exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa | + pathThroughCallable0(call, mid, kind, state, cc, ap, apa) and + out = getAnOutNodeFlow(kind, call, apa) + ) + } + + private module Subpaths { + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by + * `kind`, `sc`, `apout`, and `innercc`. + */ + pragma[nomagic] + private predicate subpaths01( + PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, + NodeEx out, FlowState sout, AccessPath apout + ) { + pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and + pathIntoCallable(arg, par, _, _, innercc, sc, _) and + paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, + pragma[only_bind_into](apout), _) and + not arg.isHidden() + } + + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by + * `kind`, `sc`, `sout`, `apout`, and `innercc`. + */ + pragma[nomagic] + private predicate subpaths02( + PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, + NodeEx out, FlowState sout, AccessPath apout + ) { + subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and + out.asNode() = kind.getAnOutNode(_) + } + + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple. + */ + pragma[nomagic] + private predicate subpaths03( + PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, + AccessPath apout + ) { + exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | + subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and + pathNode(ret, retnode, sout, innercc, sc, apout, _) and + kind = retnode.getKind() + ) + } + + private PathNodeImpl localStepToHidden(PathNodeImpl n) { + n.getASuccessorImpl() = result and + result.isHidden() and + exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | + localFlowBigStep(n1, _, n2, _, _, _, _) or + storeEx(n1, _, n2, _) or + readSetEx(n1, _, n2) + ) + } + + pragma[nomagic] + private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { + succ = pred.getANonHiddenSuccessor() and + succNode = succ.getNodeEx() + } + + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through + * a subpath between `par` and `ret` with the connecting edges `arg -> par` and + * `ret -> out` is summarized as the edge `arg -> out`. + */ + predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { + exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | + pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and + subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and + hasSuccessor(pragma[only_bind_into](arg), par, p) and + not ret.isHidden() and + pathNode(out0, o, sout, _, _, apout, _) + | + out = out0 or out = out0.projectToSink() + ) + } + + /** + * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. + */ + predicate retReach(PathNodeImpl n) { + exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) + or + exists(PathNodeImpl mid | + retReach(mid) and + n.getANonHiddenSuccessor() = mid and + not subpaths(_, mid, _, _) + ) + } + } + + /** + * Holds if data can flow from `source` to `sink`. + * + * The corresponding paths are generated from the end-points and the graph + * included in the module `PathGraph`. + */ + predicate hasFlowPath(PathNode source, PathNode sink) { + exists(PathNodeImpl flowsource, PathNodeImpl flowsink | + source = flowsource and sink = flowsink + | + flowsource.isFlowSource() and + (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and + flowsink.isFlowSink() + ) + } + + private predicate flowsTo(PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink) { + flowsource.isSource() and + flowsource.getNodeEx().asNode() = source and + (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and + flowsink.getNodeEx().asNode() = sink + } + + /** + * Holds if data can flow from `source` to `sink`. + */ + predicate hasFlow(Node source, Node sink) { flowsTo(_, _, source, sink) } + + /** + * Holds if data can flow from some source to `sink`. + */ + predicate hasFlowTo(Node sink) { sink = any(PathNodeSink n).getNodeEx().asNode() } + + /** + * Holds if data can flow from some source to `sink`. + */ + predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) } + + private predicate finalStats( + boolean fwd, int nodes, int fields, int conscand, int states, int tuples + ) { + fwd = true and + nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and + fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and + conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and + states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and + tuples = count(PathNodeImpl pn) + or + fwd = false and + nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and + fields = + count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and + conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and + states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and + tuples = count(PathNode pn) + } + + /** + * INTERNAL: Only for debugging. + * + * Calculates per-stage metrics for data flow. + */ + predicate stageStats( + int n, string stage, int nodes, int fields, int conscand, int states, int tuples + ) { + stage = "1 Fwd" and + n = 10 and + Stage1::stats(true, nodes, fields, conscand, states, tuples) + or + stage = "1 Rev" and + n = 15 and + Stage1::stats(false, nodes, fields, conscand, states, tuples) + or + stage = "2 Fwd" and + n = 20 and + Stage2::stats(true, nodes, fields, conscand, states, tuples) + or + stage = "2 Rev" and + n = 25 and + Stage2::stats(false, nodes, fields, conscand, states, tuples) + or + stage = "3 Fwd" and + n = 30 and + Stage3::stats(true, nodes, fields, conscand, states, tuples) + or + stage = "3 Rev" and + n = 35 and + Stage3::stats(false, nodes, fields, conscand, states, tuples) + or + stage = "4 Fwd" and + n = 40 and + Stage4::stats(true, nodes, fields, conscand, states, tuples) + or + stage = "4 Rev" and + n = 45 and + Stage4::stats(false, nodes, fields, conscand, states, tuples) + or + stage = "5 Fwd" and + n = 50 and + Stage5::stats(true, nodes, fields, conscand, states, tuples) + or + stage = "5 Rev" and + n = 55 and + Stage5::stats(false, nodes, fields, conscand, states, tuples) + or + stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) + or + stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) + } + + module FlowExploration { + private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2) { + exists(NodeEx node1, NodeEx node2 | + jumpStepEx(node1, node2) + or + additionalJumpStep(node1, node2) + or + additionalJumpStateStep(node1, _, node2, _) + or + // flow into callable + viableParamArgEx(_, node2, node1) + or + // flow out of a callable + viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) + | + c1 = node1.getEnclosingCallable() and + c2 = node2.getEnclosingCallable() and + c1 != c2 + ) + } + + private predicate interestingCallableSrc(DataFlowCallable c) { + exists(Node n | Config::isSource(n, _) and c = getNodeEnclosingCallable(n)) + or + exists(DataFlowCallable mid | interestingCallableSrc(mid) and callableStep(mid, c)) + } + + private predicate interestingCallableSink(DataFlowCallable c) { + exists(Node n | Config::isSink(n, _) and c = getNodeEnclosingCallable(n)) + or + exists(DataFlowCallable mid | interestingCallableSink(mid) and callableStep(c, mid)) + } + + private newtype TCallableExt = + TCallable(DataFlowCallable c) { + interestingCallableSrc(c) or + interestingCallableSink(c) + } or + TCallableSrc() or + TCallableSink() + + private predicate callableExtSrc(TCallableSrc src) { any() } + + private predicate callableExtSink(TCallableSink sink) { any() } + + private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { + exists(DataFlowCallable c1, DataFlowCallable c2 | + callableStep(c1, c2) and + ce1 = TCallable(c1) and + ce2 = TCallable(c2) + ) + or + exists(Node n | + ce1 = TCallableSrc() and + Config::isSource(n, _) and + ce2 = TCallable(getNodeEnclosingCallable(n)) + ) + or + exists(Node n | + ce2 = TCallableSink() and + Config::isSink(n, _) and + ce1 = TCallable(getNodeEnclosingCallable(n)) + ) + } + + private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { + callableExtStepFwd(ce2, ce1) + } + + private int distSrcExt(TCallableExt c) = + shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) + + private int distSinkExt(TCallableExt c) = + shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) + + private int distSrc(DataFlowCallable c) { result = distSrcExt(TCallable(c)) - 1 } + + private int distSink(DataFlowCallable c) { result = distSinkExt(TCallable(c)) - 1 } + + private newtype TPartialAccessPath = + TPartialNil(DataFlowType t) or + TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } + + /** + * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first + * element of the list and its length are tracked. If data flows from a source to + * a given node with a given `AccessPath`, this indicates the sequence of + * dereference operations needed to get from the value in the node to the + * tracked object. The final type indicates the type of the tracked object. + */ + private class PartialAccessPath extends TPartialAccessPath { + abstract string toString(); + + TypedContent getHead() { this = TPartialCons(result, _) } + + int len() { + this = TPartialNil(_) and result = 0 + or + this = TPartialCons(_, result) + } + + DataFlowType getType() { + this = TPartialNil(result) + or + exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) + } + } + + private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { + override string toString() { + exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) + } + } + + private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { + override string toString() { + exists(TypedContent tc, int len | this = TPartialCons(tc, len) | + if len = 1 + then result = "[" + tc.toString() + "]" + else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" + ) + } + } + + private newtype TRevPartialAccessPath = + TRevPartialNil() or + TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } + + /** + * Conceptually a list of `Content`s, but only the first + * element of the list and its length are tracked. + */ + private class RevPartialAccessPath extends TRevPartialAccessPath { + abstract string toString(); + + Content getHead() { this = TRevPartialCons(result, _) } + + int len() { + this = TRevPartialNil() and result = 0 + or + this = TRevPartialCons(_, result) + } + } + + private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { + override string toString() { result = "" } + } + + private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { + override string toString() { + exists(Content c, int len | this = TRevPartialCons(c, len) | + if len = 1 + then result = "[" + c.toString() + "]" + else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" + ) + } + } + + private predicate relevantState(FlowState state) { + sourceNode(_, state) or + sinkNode(_, state) or + additionalLocalStateStep(_, state, _, _) or + additionalLocalStateStep(_, _, _, state) or + additionalJumpStateStep(_, state, _, _) or + additionalJumpStateStep(_, _, _, state) + } + + private newtype TSummaryCtx1 = + TSummaryCtx1None() or + TSummaryCtx1Param(ParamNodeEx p) + + private newtype TSummaryCtx2 = + TSummaryCtx2None() or + TSummaryCtx2Some(FlowState s) { relevantState(s) } + + private newtype TSummaryCtx3 = + TSummaryCtx3None() or + TSummaryCtx3Some(PartialAccessPath ap) + + private newtype TRevSummaryCtx1 = + TRevSummaryCtx1None() or + TRevSummaryCtx1Some(ReturnPosition pos) + + private newtype TRevSummaryCtx2 = + TRevSummaryCtx2None() or + TRevSummaryCtx2Some(FlowState s) { relevantState(s) } + + private newtype TRevSummaryCtx3 = + TRevSummaryCtx3None() or + TRevSummaryCtx3Some(RevPartialAccessPath ap) + + private newtype TPartialPathNode = + TPartialPathNodeFwd( + NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, + TSummaryCtx3 sc3, PartialAccessPath ap + ) { + sourceNode(node, state) and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() and + ap = TPartialNil(node.getDataFlowType()) and + exists(explorationLimit()) + or + partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap) and + distSrc(node.getEnclosingCallable()) <= explorationLimit() + } or + TPartialPathNodeRev( + NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, + RevPartialAccessPath ap + ) { + sinkNode(node, state) and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + sc3 = TRevSummaryCtx3None() and + ap = TRevPartialNil() and + exists(explorationLimit()) + or + revPartialPathStep(_, node, state, sc1, sc2, sc3, ap) and + not clearsContentEx(node, ap.getHead()) and + ( + notExpectsContent(node) or + expectsContentEx(node, ap.getHead()) + ) and + not fullBarrier(node) and + not stateBarrier(node, state) and + distSink(node.getEnclosingCallable()) <= explorationLimit() + } + + pragma[nomagic] + private predicate partialPathNodeMk0( + NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, + TSummaryCtx3 sc3, PartialAccessPath ap + ) { + partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap) and + not fullBarrier(node) and + not stateBarrier(node, state) and + not clearsContentEx(node, ap.getHead().getContent()) and + ( + notExpectsContent(node) or + expectsContentEx(node, ap.getHead().getContent()) + ) and + if node.asNode() instanceof CastingNode + then compatibleTypes(node.getDataFlowType(), ap.getType()) + else any() + } + + /** + * A `Node` augmented with a call context, an access path, and a configuration. + */ + class PartialPathNode extends TPartialPathNode { + /** Gets a textual representation of this element. */ + string toString() { result = this.getNodeEx().toString() + this.ppAp() } + + /** + * Gets a textual representation of this element, including a textual + * representation of the call context. + */ + string toStringWithContext() { + result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() + } + + /** + * 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 + ) { + this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + + /** Gets the underlying `Node`. */ + final Node getNode() { this.getNodeEx().projectToNode() = result } + + FlowState getState() { none() } + + private NodeEx getNodeEx() { + result = this.(PartialPathNodeFwd).getNodeEx() or + result = this.(PartialPathNodeRev).getNodeEx() + } + + /** Gets a successor of this node, if any. */ + PartialPathNode getASuccessor() { none() } + + /** + * Gets the approximate distance to the nearest source measured in number + * of interprocedural steps. + */ + int getSourceDistance() { result = distSrc(this.getNodeEx().getEnclosingCallable()) } + + /** + * Gets the approximate distance to the nearest sink measured in number + * of interprocedural steps. + */ + int getSinkDistance() { result = distSink(this.getNodeEx().getEnclosingCallable()) } + + private string ppAp() { + exists(string s | + s = this.(PartialPathNodeFwd).getAp().toString() or + s = this.(PartialPathNodeRev).getAp().toString() + | + if s = "" then result = "" else result = " " + s + ) + } + + private string ppCtx() { + result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" + } + + /** Holds if this is a source in a forward-flow path. */ + predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } + + /** Holds if this is a sink in a reverse-flow path. */ + predicate isRevSink() { this.(PartialPathNodeRev).isSink() } + } + + /** + * Provides the query predicates needed to include a graph in a path-problem query. + */ + module PartialPathGraph { + /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ + query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } + } + + private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { + NodeEx node; + FlowState state; + CallContext cc; + TSummaryCtx1 sc1; + TSummaryCtx2 sc2; + TSummaryCtx3 sc3; + PartialAccessPath ap; + + PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap) } + + NodeEx getNodeEx() { result = node } + + override FlowState getState() { result = state } + + CallContext getCallContext() { result = cc } + + TSummaryCtx1 getSummaryCtx1() { result = sc1 } + + TSummaryCtx2 getSummaryCtx2() { result = sc2 } + + TSummaryCtx3 getSummaryCtx3() { result = sc3 } + + PartialAccessPath getAp() { result = ap } + + override PartialPathNodeFwd getASuccessor() { + partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), + result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp()) + } + + predicate isSource() { + sourceNode(node, state) and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() and + ap instanceof TPartialNil + } + } + + private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { + NodeEx node; + FlowState state; + TRevSummaryCtx1 sc1; + TRevSummaryCtx2 sc2; + TRevSummaryCtx3 sc3; + RevPartialAccessPath ap; + + PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap) } + + NodeEx getNodeEx() { result = node } + + override FlowState getState() { result = state } + + TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } + + TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } + + TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } + + RevPartialAccessPath getAp() { result = ap } + + override PartialPathNodeRev getASuccessor() { + revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), + this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp()) + } + + predicate isSink() { + sinkNode(node, state) and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + sc3 = TRevSummaryCtx3None() and + ap = TRevPartialNil() + } + } + + private predicate partialPathStep( + PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, + TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap + ) { + not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and + ( + localFlowStepEx(mid.getNodeEx(), node) and + state = mid.getState() and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + ap = mid.getAp() + or + additionalLocalFlowStep(mid.getNodeEx(), node) and + state = mid.getState() and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + mid.getAp() instanceof PartialAccessPathNil and + ap = TPartialNil(node.getDataFlowType()) + or + additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state) and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + mid.getAp() instanceof PartialAccessPathNil and + ap = TPartialNil(node.getDataFlowType()) + ) + or + jumpStepEx(mid.getNodeEx(), node) and + state = mid.getState() and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() and + ap = mid.getAp() + or + additionalJumpStep(mid.getNodeEx(), node) and + state = mid.getState() and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() and + mid.getAp() instanceof PartialAccessPathNil and + ap = TPartialNil(node.getDataFlowType()) + or + additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state) and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() and + mid.getAp() instanceof PartialAccessPathNil and + ap = TPartialNil(node.getDataFlowType()) + or + partialPathStoreStep(mid, _, _, node, ap) and state = mid.getState() and cc = mid.getCallContext() and sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() and + sc3 = mid.getSummaryCtx3() + or + exists(PartialAccessPath ap0, TypedContent tc | + partialPathReadStep(mid, ap0, tc, node, cc) and + state = mid.getState() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + apConsFwd(ap, tc, ap0) + ) + or + partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap) + or + partialPathOutOfCallable(mid, node, state, cc, ap) and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() + or + partialPathThroughCallable(mid, node, state, cc, ap) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() + } + + bindingset[result, i] + private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } + + pragma[inline] + private predicate partialPathStoreStep( + PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, + PartialAccessPath ap2 + ) { + exists(NodeEx midNode, DataFlowType contentType | + midNode = mid.getNodeEx() and + ap1 = mid.getAp() and + storeEx(midNode, tc, node, contentType) and + ap2.getHead() = tc and + ap2.len() = unbindInt(ap1.len() + 1) and + compatibleTypes(ap1.getType(), contentType) + ) + } + + pragma[nomagic] + private predicate apConsFwd(PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2) { + partialPathStoreStep(_, ap1, tc, _, ap2) + } + + pragma[nomagic] + private predicate partialPathReadStep( + PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc + ) { + exists(NodeEx midNode | + midNode = mid.getNodeEx() and + ap = mid.getAp() and + read(midNode, tc.getContent(), node) and + ap.getHead() = tc and + cc = mid.getCallContext() + ) + } + + private predicate partialPathOutOfCallable0( + PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, + PartialAccessPath ap + ) { + pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and + state = mid.getState() and + innercc = mid.getCallContext() and + innercc instanceof CallContextNoCall and ap = mid.getAp() - ) - } + } - pragma[noinline] - private predicate partialPathThroughCallable0( - DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | - partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _, config) and - paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap, config) - ) - } + pragma[nomagic] + private predicate partialPathOutOfCallable1( + PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, + CallContext cc, PartialAccessPath ap + ) { + exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | + partialPathOutOfCallable0(mid, pos, state, innercc, ap) and + c = pos.getCallable() and + kind = pos.getKind() and + resolveReturn(innercc, c, call) + | + if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + ) + } - private predicate partialPathThroughCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ReturnKindExt kind | - partialPathThroughCallable0(call, mid, kind, state, cc, ap, config) and - out.asNode() = kind.getAnOutNode(call) - ) - } + private predicate partialPathOutOfCallable( + PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap + ) { + exists(ReturnKindExt kind, DataFlowCall call | + partialPathOutOfCallable1(mid, call, kind, state, cc, ap) + | + out.asNode() = kind.getAnOutNode(call) + ) + } - pragma[nomagic] - private predicate revPartialPathStep( - PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, - TRevSummaryCtx3 sc3, RevPartialAccessPath ap, Configuration config - ) { - localFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - jumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - revPartialPathReadStep(mid, _, _, node, ap) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(RevPartialAccessPath ap0, Content c | - revPartialPathStoreStep(mid, ap0, c, node, config) and + pragma[noinline] + private predicate partialPathIntoArg( + PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, + DataFlowCall call, PartialAccessPath ap + ) { + exists(ArgNode arg, ArgumentPosition apos | + arg = mid.getNodeEx().asNode() and + state = mid.getState() and + cc = mid.getCallContext() and + arg.argumentOf(call, apos) and + ap = mid.getAp() and + parameterMatch(ppos, apos) + ) + } + + pragma[nomagic] + private predicate partialPathIntoCallable0( + PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, + CallContext outercc, DataFlowCall call, PartialAccessPath ap + ) { + partialPathIntoArg(mid, pos, state, outercc, call, ap) and + callable = resolveCall(call, outercc) + } + + private predicate partialPathIntoCallable( + PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, + CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, + DataFlowCall call, PartialAccessPath ap + ) { + exists(ParameterPosition pos, DataFlowCallable callable | + partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap) and + p.isParameterOf(callable, pos) and + sc1 = TSummaryCtx1Param(p) and + sc2 = TSummaryCtx2Some(state) and + sc3 = TSummaryCtx3Some(ap) + | + if recordDataFlowCallSite(call, callable) + then innercc = TSpecificCall(call) + else innercc = TSomeCall() + ) + } + + pragma[nomagic] + private predicate paramFlowsThroughInPartialPath( + ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, + TSummaryCtx3 sc3, PartialAccessPath ap + ) { + exists(PartialPathNodeFwd mid, RetNodeEx ret | + mid.getNodeEx() = ret and + kind = ret.getKind() and + state = mid.getState() and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + ap = mid.getAp() + ) + } + + pragma[noinline] + private predicate partialPathThroughCallable0( + DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, + CallContext cc, PartialAccessPath ap + ) { + exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | + partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _) and + paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap) + ) + } + + private predicate partialPathThroughCallable( + PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap + ) { + exists(DataFlowCall call, ReturnKindExt kind | + partialPathThroughCallable0(call, mid, kind, state, cc, ap) and + out.asNode() = kind.getAnOutNode(call) + ) + } + + pragma[nomagic] + private predicate revPartialPathStep( + PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, + TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, RevPartialAccessPath ap + ) { + localFlowStepEx(node, mid.getNodeEx()) and state = mid.getState() and sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and sc3 = mid.getSummaryCtx3() and - apConsRev(ap, c, ap0, config) - ) - or - exists(ParamNodeEx p | - mid.getNodeEx() = p and - viableParamArgEx(_, p, node) and + ap = mid.getAp() + or + additionalLocalFlowStep(node, mid.getNodeEx()) and state = mid.getState() and sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and sc3 = mid.getSummaryCtx3() and + mid.getAp() instanceof RevPartialAccessPathNil and + ap = TRevPartialNil() + or + additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState()) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + mid.getAp() instanceof RevPartialAccessPathNil and + ap = TRevPartialNil() + or + jumpStepEx(node, mid.getNodeEx()) and + state = mid.getState() and sc1 = TRevSummaryCtx1None() and sc2 = TRevSummaryCtx2None() and sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - or - exists(ReturnPosition pos | - revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap, config) and - pos = getReturnPosition(node.asNode()) - ) - or - revPartialPathThroughCallable(mid, node, state, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - pragma[inline] - private predicate revPartialPathReadStep( - PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, - RevPartialAccessPath ap2 - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - read(node, c, midNode, mid.getConfiguration()) and - ap2.getHead() = c and - ap2.len() = unbindInt(ap1.len() + 1) - ) - } - - pragma[nomagic] - private predicate apConsRev( - RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeRev mid | - revPartialPathReadStep(mid, ap1, c, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathStoreStep( - PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config - ) { - exists(NodeEx midNode, TypedContent tc | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - store(node, tc, midNode, _, config) and - ap.getHead() = c and - config = mid.getConfiguration() and - tc.getContent() = c - ) - } - - pragma[nomagic] - private predicate revPartialPathIntoReturn( - PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, - TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap, - Configuration config - ) { - exists(NodeEx out | - mid.getNodeEx() = out and - mid.getState() = state and - viableReturnPosOutEx(call, pos, out) and - sc1 = TRevSummaryCtx1Some(pos) and - sc2 = TRevSummaryCtx2Some(state) and - sc3 = TRevSummaryCtx3Some(ap) and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathFlowsThrough( - ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, - TRevSummaryCtx3Some sc3, RevPartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | - mid.getNodeEx() = p and - mid.getState() = state and - p.getPosition() = ppos and + ap = mid.getAp() + or + additionalJumpStep(node, mid.getNodeEx()) and + state = mid.getState() and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + sc3 = TRevSummaryCtx3None() and + mid.getAp() instanceof RevPartialAccessPathNil and + ap = TRevPartialNil() + or + additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState()) and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + sc3 = TRevSummaryCtx3None() and + mid.getAp() instanceof RevPartialAccessPathNil and + ap = TRevPartialNil() + or + revPartialPathReadStep(mid, _, _, node, ap) and + state = mid.getState() and sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } + sc3 = mid.getSummaryCtx3() + or + exists(RevPartialAccessPath ap0, Content c | + revPartialPathStoreStep(mid, ap0, c, node) and + state = mid.getState() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + apConsRev(ap, c, ap0) + ) + or + exists(ParamNodeEx p | + mid.getNodeEx() = p and + viableParamArgEx(_, p, node) and + state = mid.getState() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + sc3 = TRevSummaryCtx3None() and + ap = mid.getAp() + ) + or + exists(ReturnPosition pos | + revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap) and + pos = getReturnPosition(node.asNode()) + ) + or + revPartialPathThroughCallable(mid, node, state, ap) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() + } - pragma[nomagic] - private predicate revPartialPathThroughCallable0( - DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, - RevPartialAccessPath ap, Configuration config - ) { - exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | - revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _, config) and - revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap, config) - ) - } + pragma[inline] + private predicate revPartialPathReadStep( + PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, + RevPartialAccessPath ap2 + ) { + exists(NodeEx midNode | + midNode = mid.getNodeEx() and + ap1 = mid.getAp() and + read(node, c, midNode) and + ap2.getHead() = c and + ap2.len() = unbindInt(ap1.len() + 1) + ) + } - pragma[nomagic] - private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ArgumentPosition pos | - revPartialPathThroughCallable0(call, mid, pos, state, ap, config) and - node.asNode().(ArgNode).argumentOf(call, pos) - ) + pragma[nomagic] + private predicate apConsRev(RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2) { + revPartialPathReadStep(_, ap1, c, _, ap2) + } + + pragma[nomagic] + private predicate revPartialPathStoreStep( + PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node + ) { + exists(NodeEx midNode, TypedContent tc | + midNode = mid.getNodeEx() and + ap = mid.getAp() and + storeEx(node, tc, midNode, _) and + ap.getHead() = c and + tc.getContent() = c + ) + } + + pragma[nomagic] + private predicate revPartialPathIntoReturn( + PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, + TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap + ) { + exists(NodeEx out | + mid.getNodeEx() = out and + mid.getState() = state and + viableReturnPosOutEx(call, pos, out) and + sc1 = TRevSummaryCtx1Some(pos) and + sc2 = TRevSummaryCtx2Some(state) and + sc3 = TRevSummaryCtx3Some(ap) and + ap = mid.getAp() + ) + } + + pragma[nomagic] + private predicate revPartialPathFlowsThrough( + ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, + TRevSummaryCtx3Some sc3, RevPartialAccessPath ap + ) { + exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | + mid.getNodeEx() = p and + mid.getState() = state and + p.getPosition() = ppos and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + ap = mid.getAp() and + parameterMatch(ppos, apos) + ) + } + + pragma[nomagic] + private predicate revPartialPathThroughCallable0( + DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, + RevPartialAccessPath ap + ) { + exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | + revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _) and + revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap) + ) + } + + pragma[nomagic] + private predicate revPartialPathThroughCallable( + PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap + ) { + exists(DataFlowCall call, ArgumentPosition pos | + revPartialPathThroughCallable0(call, mid, pos, state, ap) and + node.asNode().(ArgNode).argumentOf(call, pos) + ) + } + + private predicate partialFlow(PartialPathNode source, PartialPathNode node) { + source.isFwdSource() and + node = source.getASuccessor+() + } + + private predicate revPartialFlow(PartialPathNode node, PartialPathNode sink) { + sink.isRevSink() and + node.getASuccessor+() = sink + } + + /** + * Holds if there is a partial data flow path from `source` to `node`. The + * approximate distance between `node` and the closest source is `dist` and + * is restricted to be less than or equal to `explorationLimit()`. This + * predicate completely disregards sink definitions. + * + * This predicate is intended for data-flow exploration and debugging and may + * perform poorly if the number of sources is too big and/or the exploration + * limit is set too high without using barriers. + * + * To use this in a `path-problem` query, import the module `PartialPathGraph`. + */ + predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { + partialFlow(source, node) and + dist = node.getSourceDistance() + } + + /** + * Holds if there is a partial data flow path from `node` to `sink`. The + * approximate distance between `node` and the closest sink is `dist` and + * is restricted to be less than or equal to `explorationLimit()`. This + * predicate completely disregards source definitions. + * + * This predicate is intended for data-flow exploration and debugging and may + * perform poorly if the number of sinks is too big and/or the exploration + * limit is set too high without using barriers. + * + * To use this in a `path-problem` query, import the module `PartialPathGraph`. + * + * Note that reverse flow has slightly lower precision than the corresponding + * forward flow, as reverse flow disregards type pruning among other features. + */ + predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { + revPartialFlow(node, sink) and + dist = node.getSinkDistance() + } } } - -import FlowExploration - -private predicate partialFlow( - PartialPathNode source, PartialPathNode node, Configuration configuration -) { - source.getConfiguration() = configuration and - source.isFwdSource() and - node = source.getASuccessor+() -} - -private predicate revPartialFlow( - PartialPathNode node, PartialPathNode sink, Configuration configuration -) { - sink.getConfiguration() = configuration and - sink.isRevSink() and - node.getASuccessor+() = sink -} diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl1.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl1.qll new file mode 100644 index 00000000000..e6bdc74cceb --- /dev/null +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl1.qll @@ -0,0 +1,396 @@ +/** + * DEPRECATED: Use `Make` and `MakeWithState` instead. + * + * Provides a `Configuration` class backwards-compatible interface to the data + * flow library. + */ + +private import DataFlowImplCommon +private import DataFlowImplSpecific::Private +import DataFlowImplSpecific::Public +private import DataFlowImpl +import DataFlowImplCommonPublic +import FlowStateString + +/** + * A configuration of interprocedural data flow analysis. This defines + * sources, sinks, and any other configurable aspect of the analysis. Each + * use of the global data flow library must define its own unique extension + * of this abstract class. To create a configuration, extend this class with + * a subclass whose characteristic predicate is a unique singleton string. + * For example, write + * + * ```ql + * class MyAnalysisConfiguration extends DataFlow::Configuration { + * MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" } + * // Override `isSource` and `isSink`. + * // Optionally override `isBarrier`. + * // Optionally override `isAdditionalFlowStep`. + * } + * ``` + * Conceptually, this defines a graph where the nodes are `DataFlow::Node`s and + * the edges are those data-flow steps that preserve the value of the node + * along with any additional edges defined by `isAdditionalFlowStep`. + * Specifying nodes in `isBarrier` will remove those nodes from the graph, and + * specifying nodes in `isBarrierIn` and/or `isBarrierOut` will remove in-going + * and/or out-going edges from those nodes, respectively. + * + * Then, to query whether there is flow between some `source` and `sink`, + * write + * + * ```ql + * exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink)) + * ``` + * + * Multiple configurations can coexist, but two classes extending + * `DataFlow::Configuration` should never depend on each other. One of them + * should instead depend on a `DataFlow2::Configuration`, a + * `DataFlow3::Configuration`, or a `DataFlow4::Configuration`. + */ +abstract class Configuration extends string { + bindingset[this] + Configuration() { any() } + + /** + * Holds if `source` is a relevant data flow source. + */ + predicate isSource(Node source) { none() } + + /** + * Holds if `source` is a relevant data flow source with the given initial + * `state`. + */ + predicate isSource(Node source, FlowState state) { none() } + + /** + * Holds if `sink` is a relevant data flow sink. + */ + predicate isSink(Node sink) { none() } + + /** + * Holds if `sink` is a relevant data flow sink accepting `state`. + */ + predicate isSink(Node sink, FlowState state) { none() } + + /** + * Holds if data flow through `node` is prohibited. This completely removes + * `node` from the data flow graph. + */ + predicate isBarrier(Node node) { none() } + + /** + * Holds if data flow through `node` is prohibited when the flow state is + * `state`. + */ + predicate isBarrier(Node node, FlowState state) { none() } + + /** Holds if data flow into `node` is prohibited. */ + predicate isBarrierIn(Node node) { none() } + + /** Holds if data flow out of `node` is prohibited. */ + predicate isBarrierOut(Node node) { none() } + + /** + * DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead. + * + * Holds if data flow through nodes guarded by `guard` is prohibited. + */ + deprecated predicate isBarrierGuard(BarrierGuard guard) { none() } + + /** + * DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead. + * + * Holds if data flow through nodes guarded by `guard` is prohibited when + * the flow state is `state` + */ + deprecated predicate isBarrierGuard(BarrierGuard guard, FlowState state) { none() } + + /** + * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. + */ + predicate isAdditionalFlowStep(Node node1, Node node2) { none() } + + /** + * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. + * This step is only applicable in `state1` and updates the flow state to `state2`. + */ + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + none() + } + + /** + * Holds if an arbitrary number of implicit read steps of content `c` may be + * taken at `node`. + */ + predicate allowImplicitRead(Node node, ContentSet c) { none() } + + /** + * Gets the virtual dispatch branching limit when calculating field flow. + * This can be overridden to a smaller value to improve performance (a + * value of 0 disables field flow), or a larger value to get more results. + */ + int fieldFlowBranchLimit() { result = 2 } + + /** + * Gets a data flow configuration feature to add restrictions to the set of + * valid flow paths. + * + * - `FeatureHasSourceCallContext`: + * Assume that sources have some existing call context to disallow + * conflicting return-flow directly following the source. + * - `FeatureHasSinkCallContext`: + * Assume that sinks have some existing call context to disallow + * conflicting argument-to-parameter flow directly preceding the sink. + * - `FeatureEqualSourceSinkCallContext`: + * Implies both of the above and additionally ensures that the entire flow + * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. + */ + FlowFeature getAFeature() { none() } + + /** Holds if sources should be grouped in the result of `hasFlowPath`. */ + predicate sourceGrouping(Node source, string sourceGroup) { none() } + + /** Holds if sinks should be grouped in the result of `hasFlowPath`. */ + predicate sinkGrouping(Node sink, string sinkGroup) { none() } + + /** + * Holds if data may flow from `source` to `sink` for this configuration. + */ + predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) } + + /** + * Holds if data may flow from `source` to `sink` for this configuration. + * + * The corresponding paths are generated from the end-points and the graph + * included in the module `PathGraph`. + */ + predicate hasFlowPath(PathNode source, PathNode sink) { hasFlowPath(source, sink, this) } + + /** + * Holds if data may flow from some source to `sink` for this configuration. + */ + predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) } + + /** + * Holds if data may flow from some source to `sink` for this configuration. + */ + predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } + + /** + * DEPRECATED: Use `FlowExploration` instead. + * + * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` + * measured in approximate number of interprocedural steps. + */ + deprecated int explorationLimit() { none() } + + /** + * Holds if hidden nodes should be included in the data flow graph. + * + * This feature should only be used for debugging or when the data flow graph + * is not visualized (for example in a `path-problem` query). + */ + predicate includeHiddenNodes() { none() } +} + +/** + * This class exists to prevent mutual recursion between the user-overridden + * member predicates of `Configuration` and the rest of the data-flow library. + * Good performance cannot be guaranteed in the presence of such recursion, so + * it should be replaced by using more than one copy of the data flow library. + */ +abstract private class ConfigurationRecursionPrevention extends Configuration { + bindingset[this] + ConfigurationRecursionPrevention() { any() } + + override predicate hasFlow(Node source, Node sink) { + strictcount(Node n | this.isSource(n)) < 0 + or + strictcount(Node n | this.isSource(n, _)) < 0 + or + strictcount(Node n | this.isSink(n)) < 0 + or + strictcount(Node n | this.isSink(n, _)) < 0 + or + strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0 + or + strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, _, n2, _)) < 0 + or + super.hasFlow(source, sink) + } +} + +/** A bridge class to access the deprecated `isBarrierGuard`. */ +private class BarrierGuardGuardedNodeBridge extends Unit { + abstract predicate guardedNode(Node n, Configuration config); + + abstract predicate guardedNode(Node n, FlowState state, Configuration config); +} + +private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { + deprecated override predicate guardedNode(Node n, Configuration config) { + exists(BarrierGuard g | + config.isBarrierGuard(g) and + n = g.getAGuardedNode() + ) + } + + deprecated override predicate guardedNode(Node n, FlowState state, Configuration config) { + exists(BarrierGuard g | + config.isBarrierGuard(g, state) and + n = g.getAGuardedNode() + ) + } +} + +private FlowState relevantState(Configuration config) { + config.isSource(_, result) or + config.isSink(_, result) or + config.isBarrier(_, result) or + config.isAdditionalFlowStep(_, result, _, _) or + config.isAdditionalFlowStep(_, _, _, result) +} + +private newtype TConfigState = + TMkConfigState(Configuration config, FlowState state) { + state = relevantState(config) or state instanceof FlowStateEmpty + } + +private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) } + +private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) } + +private predicate singleConfiguration() { 1 = strictcount(Configuration c) } + +private module Config implements FullStateConfigSig { + class FlowState = TConfigState; + + predicate isSource(Node source, FlowState state) { + getConfig(state).isSource(source, getState(state)) + or + getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty + } + + predicate isSink(Node sink, FlowState state) { + getConfig(state).isSink(sink, getState(state)) + or + getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty + } + + predicate isBarrier(Node node) { none() } + + predicate isBarrier(Node node, FlowState state) { + getConfig(state).isBarrier(node, getState(state)) or + getConfig(state).isBarrier(node) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state)) + } + + predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) } + + predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) } + + predicate isAdditionalFlowStep(Node node1, Node node2) { + singleConfiguration() and + any(Configuration config).isAdditionalFlowStep(node1, node2) + } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and + getConfig(state2) = getConfig(state1) + or + not singleConfiguration() and + getConfig(state1).isAdditionalFlowStep(node1, node2) and + state2 = state1 + } + + predicate allowImplicitRead(Node node, ContentSet c) { + any(Configuration config).allowImplicitRead(node, c) + } + + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } + + FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } + + predicate sourceGrouping(Node source, string sourceGroup) { + any(Configuration config).sourceGrouping(source, sourceGroup) + } + + predicate sinkGrouping(Node sink, string sinkGroup) { + any(Configuration config).sinkGrouping(sink, sinkGroup) + } + + predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() } +} + +private import Impl as I +import I + +/** + * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. + * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. + */ +class PathNode instanceof I::PathNode { + /** Gets a textual representation of this element. */ + final string toString() { result = super.toString() } + + /** + * Gets a textual representation of this element, including a textual + * representation of the call context. + */ + final string toStringWithContext() { result = super.toStringWithContext() } + + /** + * 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/). + */ + final predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + + /** Gets the underlying `Node`. */ + final Node getNode() { result = super.getNode() } + + /** Gets the `FlowState` of this node. */ + final FlowState getState() { result = getState(super.getState()) } + + /** Gets the associated configuration. */ + final Configuration getConfiguration() { result = getConfig(super.getState()) } + + /** Gets a successor of this node, if any. */ + final PathNode getASuccessor() { result = super.getASuccessor() } + + /** Holds if this node is a source. */ + final predicate isSource() { super.isSource() } + + /** Holds if this node is a grouping of source nodes. */ + final predicate isSourceGroup(string group) { super.isSourceGroup(group) } + + /** Holds if this node is a grouping of sink nodes. */ + final predicate isSinkGroup(string group) { super.isSinkGroup(group) } +} + +private predicate hasFlow(Node source, Node sink, Configuration config) { + exists(PathNode source0, PathNode sink0 | + hasFlowPath(source0, sink0, config) and + source0.getNode() = source and + sink0.getNode() = sink + ) +} + +private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) { + hasFlowPath(source, sink) and source.getConfiguration() = config +} + +private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) } + +predicate flowsTo = hasFlow/3; diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll index 1b969756b09..e6bdc74cceb 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll @@ -1,16 +1,16 @@ /** - * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from - * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed - * through the `Configuration` class. This file exists in several identical - * copies, allowing queries to use multiple `Configuration` classes that depend - * on each other without introducing mutual recursion among those configurations. + * DEPRECATED: Use `Make` and `MakeWithState` instead. + * + * Provides a `Configuration` class backwards-compatible interface to the data + * flow library. */ private import DataFlowImplCommon private import DataFlowImplSpecific::Private import DataFlowImplSpecific::Public +private import DataFlowImpl import DataFlowImplCommonPublic +import FlowStateString /** * A configuration of interprocedural data flow analysis. This defines @@ -144,6 +144,10 @@ abstract class Configuration extends string { * - `FeatureEqualSourceSinkCallContext`: * Implies both of the above and additionally ensures that the entire flow * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. */ FlowFeature getAFeature() { none() } @@ -156,7 +160,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from `source` to `sink` for this configuration. */ - predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } + predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) } /** * Holds if data may flow from `source` to `sink` for this configuration. @@ -169,9 +173,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from some source to `sink` for this configuration. */ - predicate hasFlowTo(Node sink) { - sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode() - } + predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) } /** * Holds if data may flow from some source to `sink` for this configuration. @@ -179,10 +181,12 @@ abstract class Configuration extends string { predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } /** + * DEPRECATED: Use `FlowExploration` instead. + * * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` * measured in approximate number of interprocedural steps. */ - int explorationLimit() { none() } + deprecated int explorationLimit() { none() } /** * Holds if hidden nodes should be included in the data flow graph. @@ -191,49 +195,6 @@ abstract class Configuration extends string { * is not visualized (for example in a `path-problem` query). */ predicate includeHiddenNodes() { none() } - - /** - * Holds if there is a partial data flow path from `source` to `node`. The - * approximate distance between `node` and the closest source is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards sink definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sources is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - */ - final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { - partialFlow(source, node, this) and - dist = node.getSourceDistance() - } - - /** - * Holds if there is a partial data flow path from `node` to `sink`. The - * approximate distance between `node` and the closest sink is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards source definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sinks is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - * - * Note that reverse flow has slightly lower precision than the corresponding - * forward flow, as reverse flow disregards type pruning among other features. - */ - final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { - revPartialFlow(node, sink, this) and - dist = node.getSinkDistance() - } } /** @@ -263,90 +224,6 @@ abstract private class ConfigurationRecursionPrevention extends Configuration { } } -private newtype TNodeEx = - TNodeNormal(Node n) or - TNodeImplicitRead(Node n, boolean hasRead) { - any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true] - } - -private class NodeEx extends TNodeEx { - string toString() { - result = this.asNode().toString() - or - exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") - } - - Node asNode() { this = TNodeNormal(result) } - - predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } - - Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } - - pragma[nomagic] - private DataFlowCallable getEnclosingCallable0() { - nodeEnclosingCallable(this.projectToNode(), result) - } - - pragma[inline] - DataFlowCallable getEnclosingCallable() { - pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) - } - - pragma[nomagic] - private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } - - pragma[inline] - DataFlowType getDataFlowType() { - pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) - } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -private class ArgNodeEx extends NodeEx { - ArgNodeEx() { this.asNode() instanceof ArgNode } -} - -private class ParamNodeEx extends NodeEx { - ParamNodeEx() { this.asNode() instanceof ParamNode } - - predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { - this.asNode().(ParamNode).isParameterOf(c, pos) - } - - ParameterPosition getPosition() { this.isParameterOf(_, result) } -} - -private class RetNodeEx extends NodeEx { - RetNodeEx() { this.asNode() instanceof ReturnNodeExt } - - ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } - - ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } -} - -private predicate inBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierIn(n) - | - config.isSource(n) or config.isSource(n, _) - ) -} - -private predicate outBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierOut(n) - | - config.isSink(n) or config.isSink(n, _) - ) -} - /** A bridge class to access the deprecated `isBarrierGuard`. */ private class BarrierGuardGuardedNodeBridge extends Unit { abstract predicate guardedNode(Node n, Configuration config); @@ -370,3019 +247,94 @@ private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { } } -pragma[nomagic] -private predicate fullBarrier(NodeEx node, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n) +private FlowState relevantState(Configuration config) { + config.isSource(_, result) or + config.isSink(_, result) or + config.isBarrier(_, result) or + config.isAdditionalFlowStep(_, result, _, _) or + config.isAdditionalFlowStep(_, _, _, result) +} + +private newtype TConfigState = + TMkConfigState(Configuration config, FlowState state) { + state = relevantState(config) or state instanceof FlowStateEmpty + } + +private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) } + +private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) } + +private predicate singleConfiguration() { 1 = strictcount(Configuration c) } + +private module Config implements FullStateConfigSig { + class FlowState = TConfigState; + + predicate isSource(Node source, FlowState state) { + getConfig(state).isSource(source, getState(state)) or - config.isBarrierIn(n) and - not config.isSource(n) and - not config.isSource(n, _) + getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty + } + + predicate isSink(Node sink, FlowState state) { + getConfig(state).isSink(sink, getState(state)) or - config.isBarrierOut(n) and - not config.isSink(n) and - not config.isSink(n, _) + getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty + } + + predicate isBarrier(Node node) { none() } + + predicate isBarrier(Node node, FlowState state) { + getConfig(state).isBarrier(node, getState(state)) or + getConfig(state).isBarrier(node) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state)) + } + + predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) } + + predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) } + + predicate isAdditionalFlowStep(Node node1, Node node2) { + singleConfiguration() and + any(Configuration config).isAdditionalFlowStep(node1, node2) + } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and + getConfig(state2) = getConfig(state1) or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config) - ) -} - -pragma[nomagic] -private predicate stateBarrier(NodeEx node, FlowState state, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n, state) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config) - ) -} - -pragma[nomagic] -private predicate sourceNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSource(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSource(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -pragma[nomagic] -private predicate sinkNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSink(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSink(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -/** Provides the relevant barriers for a step from `node1` to `node2`. */ -pragma[inline] -private predicate stepFilter(NodeEx node1, NodeEx node2, Configuration config) { - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) -} - -/** - * Holds if data can flow in one local step from `node1` to `node2`. - */ -private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.asNode() = n and - node2.isImplicitReadNode(n, false) and - not fullBarrier(node1, config) - ) -} - -/** - * Holds if the additional step from `node1` to `node2` does not jump between callables. - */ -private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.isImplicitReadNode(n, true) and - node2.asNode() = n and - not fullBarrier(node2, config) - ) -} - -private predicate additionalLocalStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) - ) -} - -/** - * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. - */ -private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if the additional step from `node1` to `node2` jumps between callables. - */ -private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate additionalJumpStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -pragma[nomagic] -private predicate readSet(NodeEx node1, ContentSet c, NodeEx node2, Configuration config) { - readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and - stepFilter(node1, node2, config) - or - exists(Node n | - node2.isImplicitReadNode(n, true) and - node1.isImplicitReadNode(n, _) and - config.allowImplicitRead(n, c) - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(ContentSet cs | - readSet(node1, cs, node2, config) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate clearsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - clearsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate expectsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - expectsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -pragma[nomagic] -private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } - -pragma[nomagic] -private predicate hasReadStep(Content c, Configuration config) { read(_, c, _, config) } - -pragma[nomagic] -private predicate store( - NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config -) { - store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), - contentType) and - hasReadStep(tc.getContent(), config) and - stepFilter(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { - viableReturnPosOut(call, pos, out.asNode()) -} - -pragma[nomagic] -private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { - viableParamArg(call, p.asNode(), arg.asNode()) -} - -/** - * Holds if field flow should be used for the given configuration. - */ -private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } - -private predicate hasSourceCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSourceCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate hasSinkCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSinkCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if flow from `p` to a return node of kind `kind` is allowed. - * - * We don't expect a parameter to return stored in itself, unless - * explicitly allowed - */ -bindingset[p, kind] -private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { - exists(ParameterPosition pos | p.isParameterOf(_, pos) | - not kind.(ParamUpdateReturnKind).getPosition() = pos - or - allowParameterReturnInSelfCached(p.asNode()) - ) -} - -private module Stage1 implements StageSig { - class Ap extends int { - // workaround for bad functionality-induced joins (happens when using `Unit`) - pragma[nomagic] - Ap() { this in [0 .. 1] and this < 1 } - } - - private class Cc = boolean; - - /* Begin: Stage 1 logic. */ - /** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `cc` records whether the node is reached through an - * argument in a call. - */ - private predicate fwdFlow(NodeEx node, Cc cc, Configuration config) { - sourceNode(node, _, config) and - if hasSourceCallCtx(config) then cc = true else cc = false - or - exists(NodeEx mid | fwdFlow(mid, cc, config) | - localFlowStep(mid, node, config) or - additionalLocalFlowStep(mid, node, config) or - additionalLocalStateStep(mid, _, node, _, config) - ) - or - exists(NodeEx mid | fwdFlow(mid, _, config) and cc = false | - jumpStep(mid, node, config) or - additionalJumpStep(mid, node, config) or - additionalJumpStateStep(mid, _, node, _, config) - ) - or - // store - exists(NodeEx mid | - useFieldFlow(config) and - fwdFlow(mid, cc, config) and - store(mid, _, node, _, config) - ) - or - // read - exists(ContentSet c | - fwdFlowReadSet(c, node, cc, config) and - fwdFlowConsCandSet(c, _, config) - ) - or - // flow into a callable - fwdFlowIn(_, _, _, node, config) and - cc = true - or - // flow out of a callable - fwdFlowOut(_, node, false, config) and - cc = false - or - // flow through a callable - exists(DataFlowCall call | - fwdFlowOutFromArg(call, node, config) and - fwdFlowIsEntered(call, cc, config) - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowIn( - DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p, Configuration config - ) { - // call context cannot help reduce virtual dispatch - fwdFlow(arg, cc, config) and - viableParamArgEx(call, p, arg) and - not fullBarrier(p, config) and - ( - cc = false - or - cc = true and - not reducedViableImplInCallContext(call, _, _) - ) - or - // call context may help reduce virtual dispatch - exists(DataFlowCallable target | - fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target, config) and - target = viableImplInSomeFwdFlowCallContextExt(call, config) and - cc = true - ) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - fwdFlowIn(call, _, cc, _, config) - } - - pragma[nomagic] - private predicate fwdFlowInReducedViableImplInSomeCallContext( - DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target, Configuration config - ) { - fwdFlow(arg, true, config) and - viableParamArgEx(call, p, arg) and - reducedViableImplInCallContext(call, _, _) and - target = p.getEnclosingCallable() and - not fullBarrier(p, config) - } - - /** - * Gets a viable dispatch target of `call` in the context `ctx`. This is - * restricted to those `call`s for which a context might make a difference, - * and to `ctx`s that are reachable in `fwdFlow`. - */ - pragma[nomagic] - private DataFlowCallable viableImplInSomeFwdFlowCallContextExt( - DataFlowCall call, Configuration config - ) { - exists(DataFlowCall ctx | - fwdFlowIsEntered(ctx, _, config) and - result = viableImplInCallContextExt(call, ctx) - ) - } - - private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) } - - pragma[nomagic] - private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc, Configuration config) { - exists(NodeEx mid | - fwdFlow(mid, cc, config) and - readSet(mid, c, node, config) - ) - } - - /** - * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - fwdFlow(mid, _, config) and - store(mid, tc, node, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `cs` may be interpreted in a read as the target of some store - * into `c`, in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCandSet(ContentSet cs, Content c, Configuration config) { - fwdFlowConsCand(c, config) and - c = cs.getAReadContent() - } - - pragma[nomagic] - private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { - exists(RetNodeEx ret | - fwdFlow(ret, cc, config) and - ret.getReturnPosition() = pos - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) { - exists(ReturnPosition pos | - fwdFlowReturnPosition(pos, cc, config) and - viableReturnPosOutEx(call, pos, out) and - not fullBarrier(out, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) { - fwdFlowOut(call, out, true, config) - } - - private predicate stateStepFwd(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1 | - additionalLocalStateStep(node1, state1, _, state2, config) or - additionalJumpStateStep(node1, state1, _, state2, config) - | - fwdFlow(node1, config) - ) - } - - private predicate fwdFlowState(FlowState state, Configuration config) { - sourceNode(_, state, config) - or - exists(FlowState state0 | - fwdFlowState(state0, config) and - stateStepFwd(state0, state, config) - ) - } - - /** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink. - */ - pragma[nomagic] - private predicate revFlow(NodeEx node, boolean toReturn, Configuration config) { - revFlow0(node, toReturn, config) and - fwdFlow(node, config) - } - - pragma[nomagic] - private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) { - exists(FlowState state | - fwdFlow(node, pragma[only_bind_into](config)) and - sinkNode(node, state, config) and - fwdFlowState(state, pragma[only_bind_into](config)) and - if hasSinkCallCtx(config) then toReturn = true else toReturn = false - ) - or - exists(NodeEx mid | revFlow(mid, toReturn, config) | - localFlowStep(node, mid, config) or - additionalLocalFlowStep(node, mid, config) or - additionalLocalStateStep(node, _, mid, _, config) - ) - or - exists(NodeEx mid | revFlow(mid, _, config) and toReturn = false | - jumpStep(node, mid, config) or - additionalJumpStep(node, mid, config) or - additionalJumpStateStep(node, _, mid, _, config) - ) - or - // store - exists(Content c | - revFlowStore(c, node, toReturn, config) and - revFlowConsCand(c, config) - ) - or - // read - exists(NodeEx mid, ContentSet c | - readSet(node, c, mid, config) and - fwdFlowConsCandSet(c, _, pragma[only_bind_into](config)) and - revFlow(mid, toReturn, pragma[only_bind_into](config)) - ) - or - // flow into a callable - revFlowIn(_, node, false, config) and - toReturn = false - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(pos, config) and - node.(RetNodeEx).getReturnPosition() = pos and - toReturn = true - ) - or - // flow through a callable - exists(DataFlowCall call | - revFlowInToReturn(call, node, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - /** - * Holds if `c` is the target of a read in the flow covered by `revFlow`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, ContentSet cs | - fwdFlow(node, pragma[only_bind_into](config)) and - readSet(node, cs, mid, config) and - fwdFlowConsCandSet(cs, c, pragma[only_bind_into](config)) and - revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) { - exists(NodeEx mid, TypedContent tc | - revFlow(mid, toReturn, pragma[only_bind_into](config)) and - fwdFlowConsCand(c, pragma[only_bind_into](config)) and - store(node, tc, mid, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `c` is the target of both a read and a store in the flow covered - * by `revFlow`. - */ - pragma[nomagic] - additional predicate revFlowIsReadAndStored(Content c, Configuration conf) { - revFlowConsCand(c, conf) and - revFlowStore(c, _, _, conf) - } - - pragma[nomagic] - additional predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config - ) { - fwdFlowReturnPosition(pos, _, config) and - viableReturnPosOutEx(call, pos, out) - } - - pragma[nomagic] - private predicate revFlowOut(ReturnPosition pos, Configuration config) { - exists(NodeEx out | - revFlow(out, _, config) and - viableReturnPosOutNodeCandFwd1(_, pos, out, config) - ) - } - - pragma[nomagic] - additional predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config - ) { - fwdFlowIn(call, arg, _, p, config) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate revFlowIn( - DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config - ) { - exists(ParamNodeEx p | - revFlow(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) { - revFlowIn(call, arg, true, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(NodeEx out | - revFlow(out, toReturn, config) and - fwdFlowOutFromArg(call, out, config) - ) - } - - private predicate stateStepRev(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - additionalLocalStateStep(node1, state1, node2, state2, config) or - additionalJumpStateStep(node1, state1, node2, state2, config) - | - revFlow(node1, _, pragma[only_bind_into](config)) and - revFlow(node2, _, pragma[only_bind_into](config)) and - fwdFlowState(state1, pragma[only_bind_into](config)) and - fwdFlowState(state2, pragma[only_bind_into](config)) - ) - } - - additional predicate revFlowState(FlowState state, Configuration config) { - exists(NodeEx node | - sinkNode(node, state, config) and - revFlow(node, _, pragma[only_bind_into](config)) and - fwdFlowState(state, pragma[only_bind_into](config)) - ) - or - exists(FlowState state0 | - revFlowState(state0, config) and - stateStepRev(state, state0, config) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Content c | - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - revFlow(node2, pragma[only_bind_into](config)) and - store(node1, tc, node2, contentType, config) and - c = tc.getContent() and - exists(ap1) - ) - } - - pragma[nomagic] - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) { - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - read(n1, c, n2, pragma[only_bind_into](config)) and - revFlow(n2, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, config) and - exists(ap) - } - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, _, pragma[only_bind_into](config)) and - exists(state) and - exists(ap) - } - - private predicate throughFlowNodeCand(NodeEx node, Configuration config) { - revFlow(node, true, config) and - fwdFlow(node, true, config) and - not inBarrier(node, config) and - not outBarrier(node, config) - } - - /** Holds if flow may return from `callable`. */ - pragma[nomagic] - private predicate returnFlowCallableNodeCand( - DataFlowCallable callable, ReturnKindExt kind, Configuration config - ) { - exists(RetNodeEx ret | - throughFlowNodeCand(ret, config) and - callable = ret.getEnclosingCallable() and - kind = ret.getKind() - ) - } - - /** - * Holds if flow may enter through `p` and reach a return node making `p` a - * candidate for the origin of a summary. - */ - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(DataFlowCallable c, ReturnKindExt kind | - throughFlowNodeCand(p, config) and - returnFlowCallableNodeCand(c, kind, config) and - p.getEnclosingCallable() = c and - exists(ap) and - parameterFlowThroughAllowed(p, kind) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - throughFlowNodeCand(ret, config) and - kind = ret.getKind() and - exists(argAp) and - exists(ap) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, boolean toReturn | - revFlow(arg, toReturn, config) and - revFlowInToReturn(call, arg, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, config)) and - fields = count(Content f0 | fwdFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | fwdFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, config)) and - fields = count(Content f0 | revFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | revFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | revFlow(n, b, config)) - } - /* End: Stage 1 logic. */ -} - -pragma[noinline] -private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - localFlowStep(node1, node2, config) -} - -pragma[noinline] -private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - additionalLocalFlowStep(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCand1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config -) { - Stage1::revFlow(out, config) and - Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, Configuration config -) { - exists(ReturnPosition pos | - viableReturnPosOutNodeCand1(call, pos, out, config) and - pos = ret.getReturnPosition() and - kind = pos.getKind() and - Stage1::revFlow(ret, config) and - not outBarrier(ret, config) and - not inBarrier(out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCand1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config -) { - Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and - Stage1::revFlow(arg, config) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config -) { - viableParamArgNodeCand1(call, p, arg, config) and - Stage1::revFlow(p, config) and - not outBarrier(arg, config) and - not inBarrier(p, config) -} - -/** - * Gets the amount of forward branching on the origin of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int branch(NodeEx n1, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n1, _, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) - ) -} - -/** - * Gets the amount of backward branching on the target of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int join(NodeEx n2, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n, _, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) - ) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. The - * `allowsFieldFlow` flag indicates whether the branching is within the limit - * specified by the configuration. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, ret, kind, out, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(ret, pragma[only_bind_into](config)) and - j = join(out, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. The `allowsFieldFlow` flag indicates whether - * the branching is within the limit specified by the configuration. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config -) { - flowIntoCallNodeCand1(call, arg, p, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(arg, pragma[only_bind_into](config)) and - j = join(p, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -private signature module StageSig { - class Ap; - - predicate revFlow(NodeEx node, Configuration config); - - predicate revFlowAp(NodeEx node, Ap ap, Configuration config); - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config); - - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config); - - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config); - - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ); - - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ); - - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config); -} - -private module MkStage { - class ApApprox = PrevStage::Ap; - - signature module StageParam { - class Ap; - - class ApNil extends Ap; - - bindingset[result, ap] - ApApprox getApprox(Ap ap); - - ApNil getApNil(NodeEx node); - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail); - - /** - * An approximation of `Content` that corresponds to the precision level of - * `Ap`, such that the mappings from both `Ap` and `Content` to this type - * are functional. - */ - class ApHeadContent; - - ApHeadContent getHeadContent(Ap ap); - - ApHeadContent projectToHeadContent(Content c); - - class ApOption; - - ApOption apNone(); - - ApOption apSome(Ap ap); - - class Cc; - - class CcCall extends Cc; - - // TODO: member predicate on CcCall - predicate matchesCall(CcCall cc, DataFlowCall call); - - class CcNoCall extends Cc; - - Cc ccNone(); - - CcCall ccSomeCall(); - - class LocalCc; - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc); - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ); - - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config - ); - - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config - ); - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config); - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType); - } - - module Stage implements StageSig { - import Param - - /* Begin: Stage logic. */ - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - private predicate revFlowApAlias(NodeEx node, ApApprox apa, Configuration config) { - PrevStage::revFlowAp(node, apa, config) - } - - pragma[nomagic] - private predicate flowIntoCallApa( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa, - Configuration config - ) { - flowIntoCall(call, arg, p, allowsFieldFlow, config) and - PrevStage::revFlowAp(p, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(arg, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowOutOfCallApa( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - ApApprox apa, Configuration config - ) { - flowOutOfCall(call, ret, kind, out, allowsFieldFlow, config) and - PrevStage::revFlowAp(out, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(ret, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowThroughOutOfCall( - DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, - ApApprox argApa, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa, pragma[only_bind_into](config)) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) and - matchesCall(ccc, call) - ) - } - - /** - * Holds if `node` is reachable with access path `ap` from a source in the - * configuration `config`. - * - * The call context `cc` records whether the node is reached through an - * argument in a call, and if so, `summaryCtx` and `argAp` record the - * corresponding parameter position and access path of that argument, respectively. - */ - pragma[nomagic] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa, config) and - PrevStage::revFlow(node, state, apa, config) and - filter(node, state, ap, config) - } - - pragma[inline] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, cc, summaryCtx, argAp, ap, _, config) - } - - pragma[nomagic] - private predicate fwdFlow0( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - sourceNode(node, state, config) and - (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and - argAp = apNone() and - summaryCtx = TParamNodeNone() and - ap = getApNil(node) and - apa = getApprox(ap) - or - exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | - fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0, config) and - localCc = getLocalCc(mid, cc) - | - localStep(mid, state0, node, state, true, _, config, localCc) and - ap = ap0 and - apa = apa0 - or - localStep(mid, state0, node, state, false, ap, config, localCc) and - ap0 instanceof ApNil and - apa = getApprox(ap) - ) - or - exists(NodeEx mid | - fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa, pragma[only_bind_into](config)) and - jumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(mid, state, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(mid, state0, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStateStep(mid, state0, node, state, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - // store - exists(TypedContent tc, Ap ap0 | - fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp, config) and - ap = apCons(tc, ap0) and - apa = getApprox(ap) - ) - or - // read - exists(Ap ap0, Content c | - fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp, config) and - fwdFlowConsCand(ap0, c, ap, config) and - apa = getApprox(ap) - ) - or - // flow into a callable - fwdFlowIn(_, node, state, _, cc, _, _, ap, apa, config) and - if PrevStage::parameterMayFlowThrough(node, apa, config) - then ( - summaryCtx = TParamNodeSome(node.asNode()) and - argAp = apSome(ap) - ) else ( - summaryCtx = TParamNodeNone() and argAp = apNone() - ) - or - // flow out of a callable - exists( - DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, - DataFlowCallable inner - | - fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa, config) and - flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa, config) and - inner = ret.getEnclosingCallable() and - cc = getCallContextReturn(inner, call, innercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - or - // flow through a callable - exists( - DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, ApApprox innerArgApa - | - fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowStore( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(DataFlowType contentType, ApApprox apa1 | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1, config) and - PrevStage::storeStepCand(node1, apa1, tc, node2, contentType, config) and - typecheckStore(ap1, contentType) - ) - } - - /** - * Holds if forward flow with access path `tail` reaches a store of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(TypedContent tc | - fwdFlowStore(_, tail, tc, _, _, _, _, _, config) and - tc.getContent() = c and - cons = apCons(tc, tail) - ) - } - - pragma[nomagic] - private predicate readStepCand( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - PrevStage::readStepCand(node1, c, node2, config) and - apc = projectToHeadContent(c) - } - - bindingset[node1, apc] - pragma[inline_late] - private predicate readStepCand0( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - readStepCand(node1, apc, c, node2, config) - } - - pragma[nomagic] - private predicate fwdFlowRead( - Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(ApHeadContent apc | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap, config) and - apc = getHeadContent(ap) and - readStepCand0(node1, apc, c, node2, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowIn( - DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, - ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa, Configuration config - ) { - exists(ArgNodeEx arg, boolean allowsFieldFlow | - fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa, config) and - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowRetFromArg( - RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, ApApprox argApa, - Ap ap, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - fwdFlow(pragma[only_bind_into](ret), state, ccc, - TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), - pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa), - pragma[only_bind_into](config)) and - kind = ret.getKind() and - parameterFlowThroughAllowed(summaryCtx, kind) and - argApa = getApprox(argAp) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) - ) - } - - pragma[inline] - private predicate fwdFlowThrough0( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, - Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa, config) and - fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp, config) - } - - pragma[nomagic] - private predicate fwdFlowThrough( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa, - config) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` - * and data might flow through the target callable and back out at `call`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered( - DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, - ParamNodeEx p, Ap ap, Configuration config - ) { - exists(ApApprox apa | - fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(p, apa, config) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate storeStepFwd( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config - ) { - fwdFlowStore(node1, ap1, tc, node2, _, _, _, _, config) and - ap2 = apCons(tc, ap1) and - fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _, config) - } - - private predicate readStepFwd( - NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config - ) { - fwdFlowRead(ap1, c, n1, n2, _, _, _, _, config) and - fwdFlowConsCand(ap1, c, ap2, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough0( - DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, - ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, - innerArgApa, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough( - RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, - Ap ap, Configuration config - ) { - exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | - returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa, config) and - pos = ret.getReturnPosition() and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowThroughIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap, - Configuration config - ) { - exists(ApApprox argApa | - flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), - allowsFieldFlow, argApa, pragma[only_bind_into](config)) and - fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa, - pragma[only_bind_into](config)) and - returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap, - pragma[only_bind_into](config)) and - if allowsFieldFlow = false then argAp instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowIntoCallAp( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap, - Configuration config - ) { - exists(ApApprox apa | - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - fwdFlow(arg, _, _, _, _, ap, apa, config) - ) - } - - pragma[nomagic] - private predicate flowOutOfCallAp( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, - Ap ap, Configuration config - ) { - exists(ApApprox apa | - flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa, config) and - fwdFlow(ret, _, _, _, _, ap, apa, config) and - pos = ret.getReturnPosition() - ) - } - - /** - * Holds if `node` with access path `ap` is part of a path from a source to a - * sink in the configuration `config`. - * - * The parameter `returnCtx` records whether (and how) the node must be returned - * from the enclosing callable in order to reach a sink, and if so, `returnAp` - * records the access path of the returned value. - */ - pragma[nomagic] - additional predicate revFlow( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - revFlow0(node, state, returnCtx, returnAp, ap, config) and - fwdFlow(node, state, _, _, _, ap, config) - } - - pragma[nomagic] - private predicate revFlow0( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, _, _, _, ap, config) and - sinkNode(node, state, config) and - ( - if hasSinkCallCtx(config) - then returnCtx = TReturnCtxNoFlowThrough() - else returnCtx = TReturnCtxNone() - ) and - returnAp = apNone() and - ap instanceof ApNil - or - exists(NodeEx mid, FlowState state0 | - localStep(node, state, mid, state0, true, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, ap, config) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap, pragma[only_bind_into](config)) and - localStep(node, pragma[only_bind_into](state), mid, state0, false, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, nil, pragma[only_bind_into](config)) and - ap instanceof ApNil - ) - or - exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, state, _, _, ap, config) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStep(node, mid, config) and - revFlow(pragma[only_bind_into](mid), state, _, _, nil, pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStateStep(node, state, mid, state0, config) and - revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil, - pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - // store - exists(Ap ap0, Content c | - revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp, config) and - revFlowConsCand(ap0, c, ap, config) - ) - or - // read - exists(NodeEx mid, Ap ap0 | - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - readStepFwd(node, ap, _, mid, ap0, config) - ) - or - // flow into a callable - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, state, TReturnCtxNone(), returnAp, ap, config) and - flowIntoCallAp(_, node, p, allowsFieldFlow, ap, config) and - (if allowsFieldFlow = false then ap instanceof ApNil else any()) and - returnCtx = TReturnCtxNone() - ) - or - // flow through a callable - exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, node, p, _, ap, innerReturnAp, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(_, node, pos, state, _, _, ap, config) and - if returnFlowsThrough(node, pos, state, _, _, _, ap, config) - then ( - returnCtx = TReturnCtxMaybeFlowThrough(pos) and - returnAp = apSome(ap) - ) else ( - returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() - ) - ) - } - - pragma[nomagic] - private predicate revFlowStore( - Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, - ReturnCtx returnCtx, ApOption returnAp, Configuration config - ) { - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - storeStepFwd(node, ap, tc, mid, ap0, config) and - tc.getContent() = c - } - - /** - * Holds if reverse flow with access path `tail` reaches a read of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(NodeEx mid, Ap tail0 | - revFlow(mid, _, _, _, tail, config) and - tail = pragma[only_bind_into](tail0) and - readStepFwd(_, cons, c, mid, tail0, config) - ) - } - - pragma[nomagic] - private predicate revFlowOut( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, - ApOption returnAp, Ap ap, Configuration config - ) { - exists(NodeEx out, boolean allowsFieldFlow | - revFlow(out, state, returnCtx, returnAp, ap, config) and - flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate revFlowParamToReturn( - ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap, Configuration config - ) { - revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), - pragma[only_bind_into](ap), pragma[only_bind_into](config)) and - parameterFlowThroughAllowed(p, pos.getKind()) and - PrevStage::parameterMayFlowThrough(p, getApprox(ap), config) - } - - pragma[nomagic] - private predicate revFlowThrough( - DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, - ApOption returnAp, Ap ap, Ap innerReturnAp, Configuration config - ) { - revFlowParamToReturn(p, state, pos, innerReturnAp, ap, config) and - revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned( - DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap, - Configuration config - ) { - exists(RetNodeEx ret, FlowState state, CcCall ccc | - revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap, config) and - returnFlowsThrough(ret, pos, state, ccc, _, _, ap, config) and - matchesCall(ccc, call) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Ap ap2, Content c | - PrevStage::storeStepCand(node1, _, tc, node2, contentType, config) and - revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _, config) and - revFlowConsCand(ap2, c, ap1, config) - ) - } - - predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(Ap ap1, Ap ap2 | - revFlow(node2, _, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and - readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _, - pragma[only_bind_into](config)) - ) - } - - additional predicate revFlow(NodeEx node, FlowState state, Configuration config) { - revFlow(node, state, _, _, _, config) - } - - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, _, _, ap, config) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, _, _, _, ap, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, Configuration config) { - revFlow(node, _, _, _, _, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, ap, config) - } - - private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepFwd(_, ap, tc, _, _, config) - } - - private predicate revConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepCand(_, ap, tc, _, _, config) - } - - private predicate validAp(Ap ap, Configuration config) { - revFlow(_, _, _, _, ap, config) and ap instanceof ApNil - or - exists(TypedContent head, Ap tail | - consCand(head, tail, config) and - ap = apCons(head, tail) - ) - } - - additional predicate consCand(TypedContent tc, Ap ap, Configuration config) { - revConsCand(tc, ap, config) and - validAp(ap, config) - } - - pragma[nomagic] - private predicate parameterFlowsThroughRev( - ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp, Configuration config - ) { - revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap, config) and - parameterFlowThroughAllowed(p, pos.getKind()) - } - - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(ReturnPosition pos | - returnFlowsThrough(_, pos, _, _, p, ap, _, config) and - parameterFlowsThroughRev(p, ap, pos, _, config) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - exists(ParamNodeEx p, ReturnPosition pos | - returnFlowsThrough(ret, pos, _, _, p, argAp, ap, config) and - parameterFlowsThroughRev(p, argAp, pos, ap, config) and - kind = pos.getKind() - ) - } - - pragma[nomagic] - private predicate revFlowThroughArg( - DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, - Ap ap, Configuration config - ) { - exists(ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp, config) - ) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | - revFlow(arg, state, returnCtx, returnAp, ap, config) and - revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, config)) and - fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and - states = count(FlowState state | fwdFlow(_, state, _, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | - fwdFlow(n, state, cc, summaryCtx, argAp, ap, config) - ) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, _, _, _, config)) and - fields = count(TypedContent f0 | consCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and - states = count(FlowState state | revFlow(_, state, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | - revFlow(n, state, returnCtx, retAp, ap, config) - ) - } - /* End: Stage logic. */ - } -} - -private module BooleanCallContext { - class Cc extends boolean { - Cc() { this in [true, false] } - } - - class CcCall extends Cc { - CcCall() { this = true } - } - - /** Holds if the call context may be `call`. */ - predicate matchesCall(CcCall cc, DataFlowCall call) { any() } - - class CcNoCall extends Cc { - CcNoCall() { this = false } - } - - Cc ccNone() { result = false } - - CcCall ccSomeCall() { result = true } - - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } -} - -private module Level1CallContext { - class Cc = CallContext; - - class CcCall = CallContextCall; - - pragma[inline] - predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } - - class CcNoCall = CallContextNoCall; - - Cc ccNone() { result instanceof CallContextAny } - - CcCall ccSomeCall() { result instanceof CallContextSomeCall } - - module NoLocalCallContext { - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSiteDispatch(call, c) - then result = TSpecificCall(call) - else result = TSomeCall() - } - } - - module LocalCallContext { - class LocalCc = LocalCallContext; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { - result = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - node.getEnclosingCallable()) - } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() - } - } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { - checkCallContextReturn(innercc, c, call) and - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() - } -} - -private module Stage2Param implements MkStage::StageParam { - private module PrevStage = Stage1; - - class Ap extends boolean { - Ap() { this in [true, false] } - } - - class ApNil extends Ap { - ApNil() { this = false } - } - - bindingset[result, ap] - PrevStage::Ap getApprox(Ap ap) { any() } - - ApNil getApNil(NodeEx node) { Stage1::revFlow(node, _) and exists(result) } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } - - class ApHeadContent = Unit; - - pragma[inline] - ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } - - ApHeadContent projectToHeadContent(Content c) { any() } - - class ApOption = BooleanOption; - - ApOption apNone() { result = TBooleanNone() } - - ApOption apSome(Ap ap) { result = TBooleanSome(ap) } - - import Level1CallContext - import NoLocalCallContext - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - ( - preservesValue = true and - localFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalStateStep(node1, state1, node2, state2, config) - ) and - exists(ap) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand1/6; - - predicate flowIntoCall = flowIntoCallNodeCand1/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - expectsContentEx(node, c) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - PrevStage::revFlowState(state, pragma[only_bind_into](config)) and - exists(ap) and - not stateBarrier(node, state, config) and - ( - notExpectsContent(node) - or - ap = true and - expectsContentCand(node, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage2 implements StageSig { - import MkStage::Stage -} - -pragma[nomagic] -private predicate flowOutOfCallNodeCand2( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -pragma[nomagic] -private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -private module LocalFlowBigStep { - /** - * A node where some checking is required, and hence the big-step relation - * is not allowed to step over. - */ - private class FlowCheckNode extends NodeEx { - FlowCheckNode() { - castNode(this.asNode()) or - clearsContentCached(this.asNode(), _) or - expectsContentCached(this.asNode(), _) - } - } - - /** - * Holds if `node` can be the first node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowEntry(NodeEx node, FlowState state, Configuration config) { - Stage2::revFlow(node, state, config) and - ( - sourceNode(node, state, config) - or - jumpStep(_, node, config) - or - additionalJumpStep(_, node, config) - or - additionalJumpStateStep(_, _, node, state, config) - or - node instanceof ParamNodeEx - or - node.asNode() instanceof OutNodeExt - or - Stage2::storeStepCand(_, _, _, node, _, config) - or - Stage2::readStepCand(_, _, node, config) - or - node instanceof FlowCheckNode - or - exists(FlowState s | - additionalLocalStateStep(_, s, node, state, config) and - s != state - ) - ) - } - - /** - * Holds if `node` can be the last node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowExit(NodeEx node, FlowState state, Configuration config) { - exists(NodeEx next | Stage2::revFlow(next, state, config) | - jumpStep(node, next, config) or - additionalJumpStep(node, next, config) or - flowIntoCallNodeCand2(_, node, next, _, config) or - flowOutOfCallNodeCand2(_, node, _, next, _, config) or - Stage2::storeStepCand(node, _, _, next, _, config) or - Stage2::readStepCand(node, _, next, config) - ) - or - exists(NodeEx next, FlowState s | Stage2::revFlow(next, s, config) | - additionalJumpStateStep(node, state, next, s, config) - or - additionalLocalStateStep(node, state, next, s, config) and - s != state - ) - or - Stage2::revFlow(node, state, config) and - node instanceof FlowCheckNode - or - sinkNode(node, state, config) - } - - pragma[noinline] - private predicate additionalLocalFlowStepNodeCand2( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, Configuration config - ) { - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 and - Stage2::revFlow(node1, pragma[only_bind_into](state1), false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false, - pragma[only_bind_into](config)) - or - additionalLocalStateStep(node1, state1, node2, state2, config) and - Stage2::revFlow(node1, state1, false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, state2, false, pragma[only_bind_into](config)) - } - - /** - * Holds if the local path from `node1` to `node2` is a prefix of a maximal - * subsequence of local flow steps in a dataflow path. - * - * This is the transitive closure of `[additional]localFlowStep` beginning - * at `localFlowEntry`. - */ - pragma[nomagic] - private predicate localFlowStepPlus( - NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, - Configuration config, LocalCallContext cc - ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and - ( - localFlowEntry(node1, pragma[only_bind_into](state), pragma[only_bind_into](config)) and - ( - localFlowStepNodeCand1(node1, node2, config) and - preservesValue = true and - t = node1.getDataFlowType() and // irrelevant dummy value - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - or - additionalLocalFlowStepNodeCand2(node1, state, node2, state, config) and - preservesValue = false and - t = node2.getDataFlowType() - ) and - node1 != node2 and - cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) - or - exists(NodeEx mid | - localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, - pragma[only_bind_into](config), cc) and - localFlowStepNodeCand1(mid, node2, config) and - not mid instanceof FlowCheckNode and - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - ) - or - exists(NodeEx mid | - localFlowStepPlus(node1, state, mid, _, _, pragma[only_bind_into](config), cc) and - additionalLocalFlowStepNodeCand2(mid, state, node2, state, config) and - not mid instanceof FlowCheckNode and - preservesValue = false and - t = node2.getDataFlowType() - ) - ) - } - - /** - * Holds if `node1` can step to `node2` in one or more local steps and this - * path can occur as a maximal subsequence of local steps in a dataflow path. - */ - pragma[nomagic] - predicate localFlowBigStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - DataFlowType t, Configuration config, LocalCallContext callContext - ) { - localFlowStepPlus(node1, state1, node2, preservesValue, t, config, callContext) and - localFlowExit(node2, state1, config) and - state1 = state2 - or - additionalLocalFlowStepNodeCand2(node1, state1, node2, state2, config) and - state1 != state2 and - preservesValue = false and - t = node2.getDataFlowType() and - callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) - } -} - -private import LocalFlowBigStep - -private module Stage3Param implements MkStage::StageParam { - private module PrevStage = Stage2; - - class Ap = ApproxAccessPathFront; - - class ApNil = ApproxAccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } - - class ApHeadContent = ContentApprox; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - predicate projectToHeadContent = getContentApprox/1; - - class ApOption = ApproxAccessPathFrontOption; - - ApOption apNone() { result = TApproxAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } - - import BooleanCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApproxAccessPathFrontNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand2/6; - - predicate flowIntoCall = flowIntoCallNodeCand2/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getAHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage3 implements StageSig { - import MkStage::Stage -} - -private module Stage4Param implements MkStage::StageParam { - private module PrevStage = Stage3; - - class Ap = AccessPathFront; - - class ApNil = AccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathFrontOption; - - ApOption apNone() { result = TAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } - - import BooleanCallContext - - pragma[nomagic] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) and - exists(lcc) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clearSet(NodeEx node, ContentSet c, Configuration config) { - PrevStage::revFlow(node, config) and - clearsContentCached(node.asNode(), c) - } - - pragma[nomagic] - private predicate clearContent(NodeEx node, Content c, Configuration config) { - exists(ContentSet cs | - PrevStage::readStepCand(_, pragma[only_bind_into](c), _, pragma[only_bind_into](config)) and - c = cs.getAReadContent() and - clearSet(node, cs, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clear(NodeEx node, Ap ap, Configuration config) { - clearContent(node, ap.getHead().getContent(), config) - } - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - not clear(node, ap, config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage4 implements StageSig { - import MkStage::Stage -} - -/** - * Holds if `argApf` is recorded as the summary context for flow reaching `node` - * and remains relevant for the following pruning stage. - */ -private predicate flowCandSummaryCtx( - NodeEx node, FlowState state, AccessPathFront argApf, Configuration config -) { - exists(AccessPathFront apf | - Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf, config) and - Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf, - config) - ) -} - -/** - * Holds if a length 2 access path approximation with the head `tc` is expected - * to be expensive. - */ -private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { - exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf, config)) and - nodes = - strictcount(NodeEx n, FlowState state | - Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - or - flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - ) and - accessPathApproxCostLimits(apLimit, tupleLimit) and - apLimit < tails and - tupleLimit < (tails - 1) * nodes and - not tc.forceHighPrecision() - ) -} - -private newtype TAccessPathApprox = - TNil(DataFlowType t) or - TConsNil(TypedContent tc, DataFlowType t) { - Stage4::consCand(tc, TFrontNil(t), _) and - not expensiveLen2unfolding(tc, _) - } or - TConsCons(TypedContent tc1, TypedContent tc2, int len) { - Stage4::consCand(tc1, TFrontHead(tc2), _) and - len in [2 .. accessPathLimit()] and - not expensiveLen2unfolding(tc1, _) - } or - TCons1(TypedContent tc, int len) { - len in [1 .. accessPathLimit()] and - expensiveLen2unfolding(tc, _) - } - -/** - * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only - * the first two elements of the list and its length are tracked. If data flows - * from a source to a given node with a given `AccessPathApprox`, this indicates - * the sequence of dereference operations needed to get from the value in the node - * to the tracked object. The final type indicates the type of the tracked object. - */ -abstract private class AccessPathApprox extends TAccessPathApprox { - abstract string toString(); - - abstract TypedContent getHead(); - - abstract int len(); - - abstract DataFlowType getType(); - - abstract AccessPathFront getFront(); - - /** Gets the access path obtained by popping `head` from this path, if any. */ - abstract AccessPathApprox pop(TypedContent head); -} - -private class AccessPathApproxNil extends AccessPathApprox, TNil { - private DataFlowType t; - - AccessPathApproxNil() { this = TNil(t) } - - override string toString() { result = concat(": " + ppReprType(t)) } - - override TypedContent getHead() { none() } - - override int len() { result = 0 } - - override DataFlowType getType() { result = t } - - override AccessPathFront getFront() { result = TFrontNil(t) } - - override AccessPathApprox pop(TypedContent head) { none() } -} - -abstract private class AccessPathApproxCons extends AccessPathApprox { } - -private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { - private TypedContent tc; - private DataFlowType t; - - AccessPathApproxConsNil() { this = TConsNil(tc, t) } - - override string toString() { - // The `concat` becomes "" if `ppReprType` has no result. - result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) - } - - override TypedContent getHead() { result = tc } - - override int len() { result = 1 } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } -} - -private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { - private TypedContent tc1; - private TypedContent tc2; - private int len; - - AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - - override string toString() { - if len = 2 - then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" - else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc1 } - - override int len() { result = len } - - override DataFlowType getType() { result = tc1.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc1) } - - override AccessPathApprox pop(TypedContent head) { - head = tc1 and - ( - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - } -} - -private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { - private TypedContent tc; - private int len; - - AccessPathApproxCons1() { this = TCons1(tc, len) } - - override string toString() { - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc } - - override int len() { result = len } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { - head = tc and - ( - exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2), _) | - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - or - exists(DataFlowType t | - len = 1 and - Stage4::consCand(tc, TFrontNil(t), _) and - result = TNil(t) - ) - ) - } -} - -/** Gets the access path obtained by popping `tc` from `ap`, if any. */ -private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } - -/** Gets the access path obtained by pushing `tc` onto `ap`. */ -private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } - -private newtype TAccessPathApproxOption = - TAccessPathApproxNone() or - TAccessPathApproxSome(AccessPathApprox apa) - -private class AccessPathApproxOption extends TAccessPathApproxOption { - string toString() { - this = TAccessPathApproxNone() and result = "" - or - this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) - } -} - -private module Stage5Param implements MkStage::StageParam { - private module PrevStage = Stage4; - - class Ap = AccessPathApprox; - - class ApNil = AccessPathApproxNil; - - pragma[nomagic] - PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathApproxOption; - - ApOption apNone() { result = TAccessPathApproxNone() } - - ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } - - import Level1CallContext - import LocalCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, lcc) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { any() } - - // Type checking is not necessary here as it has already been done in stage 3. - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage5 = MkStage::Stage; - -bindingset[conf, result] -private Configuration unbindConf(Configuration conf) { - exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c)) -} - -pragma[nomagic] -private predicate nodeMayUseSummary0( - NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(AccessPathApprox apa0 | - Stage5::parameterMayFlowThrough(p, _, _) and - Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0, config) and - Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), - TAccessPathApproxSome(apa), apa0, config) - ) -} - -pragma[nomagic] -private predicate nodeMayUseSummary( - NodeEx n, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::parameterMayFlowThrough(p, apa, config) and - nodeMayUseSummary0(n, p, state, apa, config) - ) -} - -private newtype TSummaryCtx = - TSummaryCtxNone() or - TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { - exists(Configuration config | - Stage5::parameterMayFlowThrough(p, ap.getApprox(), config) and - Stage5::revFlow(p, state, _, config) - ) - } - -/** - * A context for generating flow summaries. This represents flow entry through - * a specific parameter with an access path of a specific shape. - * - * Summaries are only created for parameters that may flow through. - */ -abstract private class SummaryCtx extends TSummaryCtx { - abstract string toString(); -} - -/** A summary context from which no flow summary can be generated. */ -private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { - override string toString() { result = "" } -} - -/** A summary context from which a flow summary can be generated. */ -private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParamNodeEx p; - private FlowState s; - private AccessPath ap; - - SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } - - ParameterPosition getParameterPos() { p.isParameterOf(_, result) } - - ParamNodeEx getParamNode() { result = p } - - override string toString() { result = p + ": " + ap } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** - * Gets the number of length 2 access path approximations that correspond to `apa`. - */ -private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { - exists(TypedContent tc, int len | - tc = apa.getHead() and - len = apa.len() and - result = - strictcount(AccessPathFront apf | - Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), - config) - ) - ) -} - -private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = - strictcount(NodeEx n, FlowState state | - Stage5::revFlow(n, state, apa, config) or nodeMayUseSummary(n, state, apa, config) - ) -} - -/** - * Holds if a length 2 access path approximation matching `apa` is expected - * to be expensive. - */ -private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = count1to2unfold(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - apLimit < aps and - tupleLimit < (aps - 1) * nodes - ) -} - -private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { - exists(TypedContent head | - apa.pop(head) = result and - Stage5::consCand(head, result, config) - ) -} - -/** - * Holds with `unfold = false` if a precise head-tail representation of `apa` is - * expected to be expensive. Holds with `unfold = true` otherwise. - */ -private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { - if apa.getHead().forceHighPrecision() - then unfold = true - else - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = countPotentialAps(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true - ) -} - -/** - * Gets the number of `AccessPath`s that correspond to `apa`. - */ -pragma[assume_small_delta] -private int countAps(AccessPathApprox apa, Configuration config) { - evalUnfold(apa, false, config) and - result = 1 and - (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) - or - evalUnfold(apa, false, config) and - result = count1to2unfold(apa, config) and - not expensiveLen1to2unfolding(apa, config) - or - evalUnfold(apa, true, config) and - result = countPotentialAps(apa, config) -} - -/** - * Gets the number of `AccessPath`s that would correspond to `apa` assuming - * that it is expanded to a precise head-tail representation. - */ -language[monotonicAggregates] -pragma[assume_small_delta] -private int countPotentialAps(AccessPathApprox apa, Configuration config) { - apa instanceof AccessPathApproxNil and result = 1 - or - result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) -} - -private newtype TAccessPath = - TAccessPathNil(DataFlowType t) or - TAccessPathCons(TypedContent head, AccessPath tail) { - exists(AccessPathApproxCons apa | - not evalUnfold(apa, false, _) and - head = apa.getHead() and - tail.getApprox() = getATail(apa, _) - ) - } or - TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - not expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head1 = apa.getHead() and - head2 = getATail(apa, _).getHead() - ) - } or - TAccessPathCons1(TypedContent head, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head = apa.getHead() - ) - } - -private newtype TPathNode = - pragma[assume_small_delta] - TPathNodeMid( - NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config - ) { - // A PathNode is introduced by a source ... - Stage5::revFlow(node, state, config) and - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - or - // ... or a step from an existing PathNode to another node. - exists(PathNodeMid mid | - pathStep(mid, node, state, cc, sc, ap) and - pragma[only_bind_into](config) = mid.getConfiguration() and - Stage5::revFlow(node, state, ap.getApprox(), pragma[only_bind_into](config)) - ) - } or - TPathNodeSink(NodeEx node, FlowState state, Configuration config) { - exists(PathNodeMid sink | - sink.isAtSink() and - node = sink.getNodeEx() and - state = sink.getState() and - config = sink.getConfiguration() - ) - } or - TPathNodeSourceGroup(string sourceGroup, Configuration config) { - exists(PathNodeImpl source | - sourceGroup = source.getSourceGroup() and - config = source.getConfiguration() - ) - } or - TPathNodeSinkGroup(string sinkGroup, Configuration config) { - exists(PathNodeSink sink | - sinkGroup = sink.getSinkGroup() and - config = sink.getConfiguration() - ) - } - -/** - * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a - * source to a given node with a given `AccessPath`, this indicates the sequence - * of dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ -private class AccessPath extends TAccessPath { - /** Gets the head of this access path, if any. */ - abstract TypedContent getHead(); - - /** Gets the tail of this access path, if any. */ - abstract AccessPath getTail(); - - /** Gets the front of this access path. */ - abstract AccessPathFront getFront(); - - /** Gets the approximation of this access path. */ - abstract AccessPathApprox getApprox(); - - /** Gets the length of this access path. */ - abstract int length(); - - /** Gets a textual representation of this access path. */ - abstract string toString(); - - /** Gets the access path obtained by popping `tc` from this access path, if any. */ - final AccessPath pop(TypedContent tc) { - result = this.getTail() and - tc = this.getHead() - } - - /** Gets the access path obtained by pushing `tc` onto this access path. */ - final AccessPath push(TypedContent tc) { this = result.pop(tc) } -} - -private class AccessPathNil extends AccessPath, TAccessPathNil { - private DataFlowType t; - - AccessPathNil() { this = TAccessPathNil(t) } - - DataFlowType getType() { result = t } - - override TypedContent getHead() { none() } - - override AccessPath getTail() { none() } - - override AccessPathFrontNil getFront() { result = TFrontNil(t) } - - override AccessPathApproxNil getApprox() { result = TNil(t) } - - override int length() { result = 0 } - - override string toString() { result = concat(": " + ppReprType(t)) } -} - -private class AccessPathCons extends AccessPath, TAccessPathCons { - private TypedContent head; - private AccessPath tail; - - AccessPathCons() { this = TAccessPathCons(head, tail) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { result = tail } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - pragma[assume_small_delta] - override AccessPathApproxCons getApprox() { - result = TConsNil(head, tail.(AccessPathNil).getType()) - or - result = TConsCons(head, tail.getHead(), this.length()) - or - result = TCons1(head, this.length()) - } - - pragma[assume_small_delta] - override int length() { result = 1 + tail.length() } - - private string toStringImpl(boolean needsSuffix) { - exists(DataFlowType t | - tail = TAccessPathNil(t) and - needsSuffix = false and - result = head.toString() + "]" + concat(" : " + ppReprType(t)) - ) - or - result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) - or - exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | - result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true - or - result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false - ) - or - exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | - result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true - or - result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false - ) - } - - override string toString() { - result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" - or - result = "[" + this.toStringImpl(false) - } -} - -private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { - private TypedContent head1; - private TypedContent head2; - private int len; - - AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } - - override TypedContent getHead() { result = head1 } - - override AccessPath getTail() { - Stage5::consCand(head1, result.getApprox(), _) and - result.getHead() = head2 and - result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head1) } - - override AccessPathApproxCons getApprox() { - result = TConsCons(head1, head2, len) or - result = TCons1(head1, len) - } - - override int length() { result = len } - - override string toString() { - if len = 2 - then result = "[" + head1.toString() + ", " + head2.toString() + "]" - else - result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" - } -} - -private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { - private TypedContent head; - private int len; - - AccessPathCons1() { this = TAccessPathCons1(head, len) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { - Stage5::consCand(head, result.getApprox(), _) and result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - - override int length() { result = len } - - override string toString() { - if len = 1 - then result = "[" + head.toString() + "]" - else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" - } -} - -abstract private class PathNodeImpl extends TPathNode { - /** Gets the `FlowState` of this node. */ - abstract FlowState getState(); - - /** Gets the associated configuration. */ - abstract Configuration getConfiguration(); - - /** Holds if this node is a source. */ - abstract predicate isSource(); - - abstract PathNodeImpl getASuccessorImpl(); - - private PathNodeImpl getASuccessorIfHidden() { - this.isHidden() and - result = this.getASuccessorImpl() - } - - pragma[nomagic] - private PathNodeImpl getANonHiddenSuccessor0() { - result = this.getASuccessorIfHidden*() and - not result.isHidden() - } - - final PathNodeImpl getANonHiddenSuccessor() { - result = this.getASuccessorImpl().getANonHiddenSuccessor0() and - not this.isHidden() - } - - abstract NodeEx getNodeEx(); - - predicate isHidden() { - not this.getConfiguration().includeHiddenNodes() and - ( - hiddenNode(this.getNodeEx().asNode()) and - not this.isSource() and - not this instanceof PathNodeSink - or - this.getNodeEx() instanceof TNodeImplicitRead - ) - } - - string getSourceGroup() { - this.isSource() and - this.getConfiguration().sourceGrouping(this.getNodeEx().asNode(), result) + not singleConfiguration() and + getConfig(state1).isAdditionalFlowStep(node1, node2) and + state2 = state1 } - predicate isFlowSource() { - this.isSource() and not exists(this.getSourceGroup()) - or - this instanceof PathNodeSourceGroup + predicate allowImplicitRead(Node node, ContentSet c) { + any(Configuration config).allowImplicitRead(node, c) } - predicate isFlowSink() { - this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or - this instanceof PathNodeSinkGroup - } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } - private string ppAp() { - this instanceof PathNodeSink and result = "" - or - exists(string s | s = this.(PathNodeMid).getAp().toString() | - if s = "" then result = "" else result = " " + s - ) - } + FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } - private string ppCtx() { - this instanceof PathNodeSink and result = "" - or - result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + predicate sourceGrouping(Node source, string sourceGroup) { + any(Configuration config).sourceGrouping(source, sourceGroup) } - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + predicate sinkGrouping(Node sink, string sinkGroup) { + any(Configuration config).sinkGrouping(sink, sinkGroup) } -} - -/** Holds if `n` can reach a sink. */ -private predicate directReach(PathNodeImpl n) { - n instanceof PathNodeSink or - n instanceof PathNodeSinkGroup or - directReach(n.getANonHiddenSuccessor()) -} - -/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ -private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } -/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { - n1.getANonHiddenSuccessor() = n2 and directReach(n2) + predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() } } -private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) +private import Impl as I +import I /** * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. */ -class PathNode instanceof PathNodeImpl { - PathNode() { reach(this) } - +class PathNode instanceof I::PathNode { /** Gets a textual representation of this element. */ final string toString() { result = super.toString() } @@ -3406,1548 +358,39 @@ class PathNode instanceof PathNodeImpl { } /** Gets the underlying `Node`. */ - final Node getNode() { super.getNodeEx().projectToNode() = result } + final Node getNode() { result = super.getNode() } /** Gets the `FlowState` of this node. */ - final FlowState getState() { result = super.getState() } + final FlowState getState() { result = getState(super.getState()) } /** Gets the associated configuration. */ - final Configuration getConfiguration() { result = super.getConfiguration() } + final Configuration getConfiguration() { result = getConfig(super.getState()) } /** Gets a successor of this node, if any. */ - final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } + final PathNode getASuccessor() { result = super.getASuccessor() } /** Holds if this node is a source. */ final predicate isSource() { super.isSource() } /** Holds if this node is a grouping of source nodes. */ - final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group, _) } + final predicate isSourceGroup(string group) { super.isSourceGroup(group) } /** Holds if this node is a grouping of sink nodes. */ - final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group, _) } + final predicate isSinkGroup(string group) { super.isSinkGroup(group) } } -/** - * Provides the query predicates needed to include a graph in a path-problem query. - */ -module PathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } - - /** Holds if `n` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode n, string key, string val) { - key = "semmle.label" and val = n.toString() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { - Subpaths::subpaths(arg, par, ret, out) - } -} - -/** - * An intermediate flow graph node. This is a triple consisting of a `Node`, - * a `CallContext`, and a `Configuration`. - */ -private class PathNodeMid extends PathNodeImpl, TPathNodeMid { - NodeEx node; - FlowState state; - CallContext cc; - SummaryCtx sc; - AccessPath ap; - Configuration config; - - PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - SummaryCtx getSummaryCtx() { result = sc } - - AccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - private PathNodeMid getSuccMid() { - pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx(), result.getAp()) and - result.getConfiguration() = unbindConf(this.getConfiguration()) - } - - override PathNodeImpl getASuccessorImpl() { - // an intermediate step to another intermediate node - result = this.getSuccMid() - or - // a final step to a sink - result = this.getSuccMid().projectToSink() - } - - override predicate isSource() { - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - } - - predicate isAtSink() { - sinkNode(node, state, config) and - ap instanceof AccessPathNil and - if hasSinkCallCtx(config) - then - // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` - // is exactly what we need to check. This also implies - // `sc instanceof SummaryCtxNone`. - // For `FeatureEqualSourceSinkCallContext` the initial call context was - // set to `CallContextSomeCall` and jumps are disallowed, so - // `cc instanceof CallContextNoCall` never holds. On the other hand, - // in this case there's never any need to enter a call except to identify - // a summary, so the condition in `pathIntoCallable` enforces this, which - // means that `sc instanceof SummaryCtxNone` holds if and only if we are - // in the call context of the source. - sc instanceof SummaryCtxNone or - cc instanceof CallContextNoCall - else any() - } - - PathNodeSink projectToSink() { - this.isAtSink() and - result.getNodeEx() = node and - result.getState() = state and - result.getConfiguration() = unbindConf(config) - } -} - -/** - * A flow graph node corresponding to a sink. This is disjoint from the - * intermediate nodes in order to uniquely correspond to a given sink by - * excluding the `CallContext`. - */ -private class PathNodeSink extends PathNodeImpl, TPathNodeSink { - NodeEx node; - FlowState state; - Configuration config; - - PathNodeSink() { this = TPathNodeSink(node, state, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result = TPathNodeSinkGroup(this.getSinkGroup(), config) - } - - override predicate isSource() { sourceNode(node, state, config) } - - string getSinkGroup() { config.sinkGrouping(node.asNode(), result) } -} - -private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { - string sourceGroup; - Configuration config; - - PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result.getSourceGroup() = sourceGroup and - result.getConfiguration() = config - } - - override predicate isSource() { none() } - - override string toString() { result = sourceGroup } - - override 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 - } -} - -private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { - string sinkGroup; - Configuration config; - - PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { none() } - - override predicate isSource() { none() } - - override string toString() { result = sinkGroup } - - override 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 - } -} - -private predicate pathNode( - PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, - Configuration conf, LocalCallContext localCC -) { - midnode = mid.getNodeEx() and - state = mid.getState() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - midnode.getEnclosingCallable()) and - ap = mid.getAp() -} - -/** - * Holds if data may flow from `mid` to `node`. The last step in or out of - * a callable is recorded by `cc`. - */ -pragma[assume_small_delta] -pragma[nomagic] -private predicate pathStep( - PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap -) { - exists(NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC | - pathNode(mid, midnode, state0, cc, sc, ap, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, true, _, conf, localCC) - ) - or - exists( - AccessPath ap0, NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC - | - pathNode(mid, midnode, state0, cc, sc, ap0, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), conf, - localCC) and - ap0 instanceof AccessPathNil - ) - or - jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - ap = mid.getAp() - or - additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, mid.getConfiguration()) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - pathIntoCallable(mid, node, state, _, cc, sc, _, _) and ap = mid.getAp() - or - pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone - or - pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() -} - -pragma[nomagic] -private predicate pathReadStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - tc = ap0.getHead() and - Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -pragma[nomagic] -private predicate pathStoreStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -private predicate pathOutOfCallable0( - PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa, - Configuration config -) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - apa = mid.getAp().getApprox() and - config = mid.getConfiguration() -} - -pragma[nomagic] -private predicate pathOutOfCallable1( - PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPathApprox apa, Configuration config -) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - pathOutOfCallable0(mid, pos, state, innercc, apa, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() +private predicate hasFlow(Node source, Node sink, Configuration config) { + exists(PathNode source0, PathNode sink0 | + hasFlowPath(source0, sink0, config) and + source0.getNode() = source and + sink0.getNode() = sink ) } -pragma[noinline] -private NodeEx getAnOutNodeFlow( - ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config -) { - result.asNode() = kind.getAnOutNode(call) and - Stage5::revFlow(result, _, apa, config) +private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) { + hasFlowPath(source, sink) and source.getConfiguration() = config } -/** - * Holds if data may flow from `mid` to `out`. The last step of this path - * is a return from a callable and is recorded by `cc`, if needed. - */ -pragma[noinline] -private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { - exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | - pathOutOfCallable1(mid, call, kind, state, cc, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} +private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) } -/** - * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. - */ -pragma[noinline] -private predicate pathIntoArg( - PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(ArgNodeEx arg, ArgumentPosition apos | - pathNode(mid, arg, state, cc, _, ap, config, _) and - arg.asNode().(ArgNode).argumentOf(call, apos) and - apa = ap.getApprox() and - parameterMatch(ppos, apos) - ) -} - -pragma[nomagic] -private predicate parameterCand( - DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::revFlow(p, _, apa, config) and - p.isParameterOf(callable, pos) - ) -} - -pragma[nomagic] -private predicate pathIntoCallable0( - PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, AccessPath ap, Configuration config -) { - exists(AccessPathApprox apa | - pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - callable = resolveCall(call, outercc) and - parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa), - pragma[only_bind_into](config)) - ) -} - -/** - * Holds if data may flow from `mid` to `p` through `call`. The contexts - * before and after entering the callable are `outercc` and `innercc`, - * respectively. - */ -pragma[nomagic] -private predicate pathIntoCallable( - PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, - SummaryCtx sc, DataFlowCall call, Configuration config -) { - exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | - pathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - ( - sc = TSummaryCtxSome(p, state, ap) - or - not exists(TSummaryCtxSome(p, state, ap)) and - sc = TSummaryCtxNone() and - // When the call contexts of source and sink needs to match then there's - // never any reason to enter a callable except to find a summary. See also - // the comment in `PathNodeMid::isAtSink`. - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) -} - -/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ -pragma[nomagic] -private predicate paramFlowsThrough( - ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, - AccessPathApprox apa, Configuration config -) { - exists(RetNodeEx ret | - pathNode(_, ret, state, cc, sc, ap, config, _) and - kind = ret.getKind() and - apa = ap.getApprox() and - parameterFlowThroughAllowed(sc.getParamNode(), kind) - ) -} - -pragma[nomagic] -private predicate pathThroughCallable0( - DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(CallContext innercc, SummaryCtx sc | - pathIntoCallable(mid, _, _, cc, innercc, sc, call, config) and - paramFlowsThrough(kind, state, innercc, sc, ap, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` through a callable to the node `out`. - * The context `cc` is restored to its value prior to entering the callable. - */ -pragma[noinline] -private predicate pathThroughCallable( - PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap -) { - exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config | - pathThroughCallable0(call, mid, kind, state, cc, ap, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -private module Subpaths { - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths01( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - exists(Configuration config | - pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and - pathIntoCallable(arg, par, _, _, innercc, sc, _, config) and - paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, - pragma[only_bind_into](apout), _, unbindConf(config)) and - not arg.isHidden() - ) - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `sout`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths02( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and - out.asNode() = kind.getAnOutNode(_) - } - - pragma[nomagic] - private Configuration getPathNodeConf(PathNodeImpl n) { result = n.getConfiguration() } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple. - */ - pragma[nomagic] - private predicate subpaths03( - PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, AccessPath apout - ) { - exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | - subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and - pathNode(ret, retnode, sout, innercc, sc, apout, unbindConf(getPathNodeConf(arg)), _) and - kind = retnode.getKind() - ) - } - - private PathNodeImpl localStepToHidden(PathNodeImpl n) { - n.getASuccessorImpl() = result and - result.isHidden() and - exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | - localFlowBigStep(n1, _, n2, _, _, _, _, _) or - store(n1, _, n2, _, _) or - readSet(n1, _, n2, _) - ) - } - - pragma[nomagic] - private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { - succ = pred.getANonHiddenSuccessor() and - succNode = succ.getNodeEx() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { - exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | - pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and - subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and - hasSuccessor(pragma[only_bind_into](arg), par, p) and - not ret.isHidden() and - pathNode(out0, o, sout, _, _, apout, _, _) - | - out = out0 or out = out0.projectToSink() - ) - } - - /** - * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. - */ - predicate retReach(PathNodeImpl n) { - exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) - or - exists(PathNodeImpl mid | - retReach(mid) and - n.getANonHiddenSuccessor() = mid and - not subpaths(_, mid, _, _) - ) - } -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -private predicate hasFlowPath( - PathNodeImpl flowsource, PathNodeImpl flowsink, Configuration configuration -) { - flowsource.isFlowSource() and - flowsource.getConfiguration() = configuration and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.isFlowSink() -} - -private predicate flowsTo( - PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink, - Configuration configuration -) { - flowsource.isSource() and - flowsource.getConfiguration() = configuration and - flowsource.getNodeEx().asNode() = source and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.getNodeEx().asNode() = sink -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -predicate flowsTo(Node source, Node sink, Configuration configuration) { - flowsTo(_, _, source, sink, configuration) -} - -private predicate finalStats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples -) { - fwd = true and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and - tuples = count(PathNodeImpl pn) - or - fwd = false and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and - tuples = count(PathNode pn) -} - -/** - * INTERNAL: Only for debugging. - * - * Calculates per-stage metrics for data flow. - */ -predicate stageStats( - int n, string stage, int nodes, int fields, int conscand, int states, int tuples, - Configuration config -) { - stage = "1 Fwd" and - n = 10 and - Stage1::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "1 Rev" and - n = 15 and - Stage1::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Fwd" and - n = 20 and - Stage2::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Rev" and - n = 25 and - Stage2::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Fwd" and - n = 30 and - Stage3::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Rev" and - n = 35 and - Stage3::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Fwd" and - n = 40 and - Stage4::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Rev" and - n = 45 and - Stage4::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Fwd" and - n = 50 and - Stage5::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Rev" and - n = 55 and - Stage5::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) - or - stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) -} - -private module FlowExploration { - private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - jumpStep(node1, node2, config) - or - additionalJumpStep(node1, node2, config) - or - additionalJumpStateStep(node1, _, node2, _, config) - or - // flow into callable - viableParamArgEx(_, node2, node1) - or - // flow out of a callable - viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) - | - c1 = node1.getEnclosingCallable() and - c2 = node2.getEnclosingCallable() and - c1 != c2 - ) - } - - private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSource(n) or config.isSource(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSrc(mid, config) and callableStep(mid, c, config) - ) - } - - private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSink(n) or config.isSink(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSink(mid, config) and callableStep(c, mid, config) - ) - } - - private newtype TCallableExt = - TCallable(DataFlowCallable c, Configuration config) { - interestingCallableSrc(c, config) or - interestingCallableSink(c, config) - } or - TCallableSrc() or - TCallableSink() - - private predicate callableExtSrc(TCallableSrc src) { any() } - - private predicate callableExtSink(TCallableSink sink) { any() } - - private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { - exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | - callableStep(c1, c2, config) and - ce1 = TCallable(c1, pragma[only_bind_into](config)) and - ce2 = TCallable(c2, pragma[only_bind_into](config)) - ) - or - exists(Node n, Configuration config | - ce1 = TCallableSrc() and - (config.isSource(n) or config.isSource(n, _)) and - ce2 = TCallable(getNodeEnclosingCallable(n), config) - ) - or - exists(Node n, Configuration config | - ce2 = TCallableSink() and - (config.isSink(n) or config.isSink(n, _)) and - ce1 = TCallable(getNodeEnclosingCallable(n), config) - ) - } - - private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { - callableExtStepFwd(ce2, ce1) - } - - private int distSrcExt(TCallableExt c) = - shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) - - private int distSinkExt(TCallableExt c) = - shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) - - private int distSrc(DataFlowCallable c, Configuration config) { - result = distSrcExt(TCallable(c, config)) - 1 - } - - private int distSink(DataFlowCallable c, Configuration config) { - result = distSinkExt(TCallable(c, config)) - 1 - } - - private newtype TPartialAccessPath = - TPartialNil(DataFlowType t) or - TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first - * element of the list and its length are tracked. If data flows from a source to - * a given node with a given `AccessPath`, this indicates the sequence of - * dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ - private class PartialAccessPath extends TPartialAccessPath { - abstract string toString(); - - TypedContent getHead() { this = TPartialCons(result, _) } - - int len() { - this = TPartialNil(_) and result = 0 - or - this = TPartialCons(_, result) - } - - DataFlowType getType() { - this = TPartialNil(result) - or - exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) - } - } - - private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { - override string toString() { - exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) - } - } - - private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { - override string toString() { - exists(TypedContent tc, int len | this = TPartialCons(tc, len) | - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private newtype TRevPartialAccessPath = - TRevPartialNil() or - TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `Content`s, but only the first - * element of the list and its length are tracked. - */ - private class RevPartialAccessPath extends TRevPartialAccessPath { - abstract string toString(); - - Content getHead() { this = TRevPartialCons(result, _) } - - int len() { - this = TRevPartialNil() and result = 0 - or - this = TRevPartialCons(_, result) - } - } - - private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { - override string toString() { result = "" } - } - - private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { - override string toString() { - exists(Content c, int len | this = TRevPartialCons(c, len) | - if len = 1 - then result = "[" + c.toString() + "]" - else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private predicate relevantState(FlowState state) { - sourceNode(_, state, _) or - sinkNode(_, state, _) or - additionalLocalStateStep(_, state, _, _, _) or - additionalLocalStateStep(_, _, _, state, _) or - additionalJumpStateStep(_, state, _, _, _) or - additionalJumpStateStep(_, _, _, state, _) - } - - private newtype TSummaryCtx1 = - TSummaryCtx1None() or - TSummaryCtx1Param(ParamNodeEx p) - - private newtype TSummaryCtx2 = - TSummaryCtx2None() or - TSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TSummaryCtx3 = - TSummaryCtx3None() or - TSummaryCtx3Some(PartialAccessPath ap) - - private newtype TRevSummaryCtx1 = - TRevSummaryCtx1None() or - TRevSummaryCtx1Some(ReturnPosition pos) - - private newtype TRevSummaryCtx2 = - TRevSummaryCtx2None() or - TRevSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TRevSummaryCtx3 = - TRevSummaryCtx3None() or - TRevSummaryCtx3Some(RevPartialAccessPath ap) - - private newtype TPartialPathNode = - TPartialPathNodeFwd( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = TPartialNil(node.getDataFlowType()) and - exists(config.explorationLimit()) - or - partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap, config) and - distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() - } or - TPartialPathNodeRev( - NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, - RevPartialAccessPath ap, Configuration config - ) { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() and - exists(config.explorationLimit()) - or - revPartialPathStep(_, node, state, sc1, sc2, sc3, ap, config) and - not clearsContentEx(node, ap.getHead()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead()) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() - } - - pragma[nomagic] - private predicate partialPathNodeMk0( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap, config) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - not clearsContentEx(node, ap.getHead().getContent()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead().getContent()) - ) and - if node.asNode() instanceof CastingNode - then compatibleTypes(node.getDataFlowType(), ap.getType()) - else any() - } - - /** - * A `Node` augmented with a call context, an access path, and a configuration. - */ - class PartialPathNode extends TPartialPathNode { - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() - } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - /** Gets the underlying `Node`. */ - final Node getNode() { this.getNodeEx().projectToNode() = result } - - FlowState getState() { none() } - - private NodeEx getNodeEx() { - result = this.(PartialPathNodeFwd).getNodeEx() or - result = this.(PartialPathNodeRev).getNodeEx() - } - - /** Gets the associated configuration. */ - Configuration getConfiguration() { none() } - - /** Gets a successor of this node, if any. */ - PartialPathNode getASuccessor() { none() } - - /** - * Gets the approximate distance to the nearest source measured in number - * of interprocedural steps. - */ - int getSourceDistance() { - result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - /** - * Gets the approximate distance to the nearest sink measured in number - * of interprocedural steps. - */ - int getSinkDistance() { - result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - private string ppAp() { - exists(string s | - s = this.(PartialPathNodeFwd).getAp().toString() or - s = this.(PartialPathNodeRev).getAp().toString() - | - if s = "" then result = "" else result = " " + s - ) - } - - private string ppCtx() { - result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" - } - - /** Holds if this is a source in a forward-flow path. */ - predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } - - /** Holds if this is a sink in a reverse-flow path. */ - predicate isRevSink() { this.(PartialPathNodeRev).isSink() } - } - - /** - * Provides the query predicates needed to include a graph in a path-problem query. - */ - module PartialPathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } - } - - private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { - NodeEx node; - FlowState state; - CallContext cc; - TSummaryCtx1 sc1; - TSummaryCtx2 sc2; - TSummaryCtx3 sc3; - PartialAccessPath ap; - Configuration config; - - PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - TSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TSummaryCtx3 getSummaryCtx3() { result = sc3 } - - PartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeFwd getASuccessor() { - partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp(), - result.getConfiguration()) - } - - predicate isSource() { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap instanceof TPartialNil - } - } - - private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { - NodeEx node; - FlowState state; - TRevSummaryCtx1 sc1; - TRevSummaryCtx2 sc2; - TRevSummaryCtx3 sc3; - RevPartialAccessPath ap; - Configuration config; - - PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } - - RevPartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeRev getASuccessor() { - revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), - this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp(), this.getConfiguration()) - } - - predicate isSink() { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() - } - } - - private predicate partialPathStep( - PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, - TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and - ( - localFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - ) - or - jumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - partialPathStoreStep(mid, _, _, node, ap) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(PartialAccessPath ap0, TypedContent tc | - partialPathReadStep(mid, ap0, tc, node, cc, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsFwd(ap, tc, ap0, config) - ) - or - partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap, config) - or - partialPathOutOfCallable(mid, node, state, cc, ap, config) and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() - or - partialPathThroughCallable(mid, node, state, cc, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - bindingset[result, i] - private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } - - pragma[inline] - private predicate partialPathStoreStep( - PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, - PartialAccessPath ap2 - ) { - exists(NodeEx midNode, DataFlowType contentType | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - store(midNode, tc, node, contentType, mid.getConfiguration()) and - ap2.getHead() = tc and - ap2.len() = unbindInt(ap1.len() + 1) and - compatibleTypes(ap1.getType(), contentType) - ) - } - - pragma[nomagic] - private predicate apConsFwd( - PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeFwd mid | - partialPathStoreStep(mid, ap1, tc, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate partialPathReadStep( - PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc, - Configuration config - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and - ap.getHead() = tc and - pragma[only_bind_into](config) = mid.getConfiguration() and - cc = mid.getCallContext() - ) - } - - private predicate partialPathOutOfCallable0( - PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, - PartialAccessPath ap, Configuration config - ) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - ap = mid.getAp() and - config = mid.getConfiguration() - } - - pragma[nomagic] - private predicate partialPathOutOfCallable1( - PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - partialPathOutOfCallable0(mid, pos, state, innercc, ap, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) - } - - private predicate partialPathOutOfCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(ReturnKindExt kind, DataFlowCall call | - partialPathOutOfCallable1(mid, call, kind, state, cc, ap, config) - | - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[noinline] - private predicate partialPathIntoArg( - PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ArgNode arg, ArgumentPosition apos | - arg = mid.getNodeEx().asNode() and - state = mid.getState() and - cc = mid.getCallContext() and - arg.argumentOf(call, apos) and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate partialPathIntoCallable0( - PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - partialPathIntoArg(mid, pos, state, outercc, call, ap, config) and - callable = resolveCall(call, outercc) - } - - private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, - CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ParameterPosition pos, DataFlowCallable callable | - partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - sc1 = TSummaryCtx1Param(p) and - sc2 = TSummaryCtx2Some(state) and - sc3 = TSummaryCtx3Some(ap) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) - } - - pragma[nomagic] - private predicate paramFlowsThroughInPartialPath( - ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeFwd mid, RetNodeEx ret | - mid.getNodeEx() = ret and - kind = ret.getKind() and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() and - ap = mid.getAp() - ) - } - - pragma[noinline] - private predicate partialPathThroughCallable0( - DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | - partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _, config) and - paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap, config) - ) - } - - private predicate partialPathThroughCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ReturnKindExt kind | - partialPathThroughCallable0(call, mid, kind, state, cc, ap, config) and - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[nomagic] - private predicate revPartialPathStep( - PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, - TRevSummaryCtx3 sc3, RevPartialAccessPath ap, Configuration config - ) { - localFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - jumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - revPartialPathReadStep(mid, _, _, node, ap) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(RevPartialAccessPath ap0, Content c | - revPartialPathStoreStep(mid, ap0, c, node, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsRev(ap, c, ap0, config) - ) - or - exists(ParamNodeEx p | - mid.getNodeEx() = p and - viableParamArgEx(_, p, node) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - or - exists(ReturnPosition pos | - revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap, config) and - pos = getReturnPosition(node.asNode()) - ) - or - revPartialPathThroughCallable(mid, node, state, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - pragma[inline] - private predicate revPartialPathReadStep( - PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, - RevPartialAccessPath ap2 - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - read(node, c, midNode, mid.getConfiguration()) and - ap2.getHead() = c and - ap2.len() = unbindInt(ap1.len() + 1) - ) - } - - pragma[nomagic] - private predicate apConsRev( - RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeRev mid | - revPartialPathReadStep(mid, ap1, c, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathStoreStep( - PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config - ) { - exists(NodeEx midNode, TypedContent tc | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - store(node, tc, midNode, _, config) and - ap.getHead() = c and - config = mid.getConfiguration() and - tc.getContent() = c - ) - } - - pragma[nomagic] - private predicate revPartialPathIntoReturn( - PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, - TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap, - Configuration config - ) { - exists(NodeEx out | - mid.getNodeEx() = out and - mid.getState() = state and - viableReturnPosOutEx(call, pos, out) and - sc1 = TRevSummaryCtx1Some(pos) and - sc2 = TRevSummaryCtx2Some(state) and - sc3 = TRevSummaryCtx3Some(ap) and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathFlowsThrough( - ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, - TRevSummaryCtx3Some sc3, RevPartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | - mid.getNodeEx() = p and - mid.getState() = state and - p.getPosition() = ppos and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable0( - DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, - RevPartialAccessPath ap, Configuration config - ) { - exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | - revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _, config) and - revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap, config) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ArgumentPosition pos | - revPartialPathThroughCallable0(call, mid, pos, state, ap, config) and - node.asNode().(ArgNode).argumentOf(call, pos) - ) - } -} - -import FlowExploration - -private predicate partialFlow( - PartialPathNode source, PartialPathNode node, Configuration configuration -) { - source.getConfiguration() = configuration and - source.isFwdSource() and - node = source.getASuccessor+() -} - -private predicate revPartialFlow( - PartialPathNode node, PartialPathNode sink, Configuration configuration -) { - sink.getConfiguration() = configuration and - sink.isRevSink() and - node.getASuccessor+() = sink -} +predicate flowsTo = hasFlow/3; diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll index 1b969756b09..e6bdc74cceb 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll @@ -1,16 +1,16 @@ /** - * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from - * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed - * through the `Configuration` class. This file exists in several identical - * copies, allowing queries to use multiple `Configuration` classes that depend - * on each other without introducing mutual recursion among those configurations. + * DEPRECATED: Use `Make` and `MakeWithState` instead. + * + * Provides a `Configuration` class backwards-compatible interface to the data + * flow library. */ private import DataFlowImplCommon private import DataFlowImplSpecific::Private import DataFlowImplSpecific::Public +private import DataFlowImpl import DataFlowImplCommonPublic +import FlowStateString /** * A configuration of interprocedural data flow analysis. This defines @@ -144,6 +144,10 @@ abstract class Configuration extends string { * - `FeatureEqualSourceSinkCallContext`: * Implies both of the above and additionally ensures that the entire flow * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. */ FlowFeature getAFeature() { none() } @@ -156,7 +160,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from `source` to `sink` for this configuration. */ - predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } + predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) } /** * Holds if data may flow from `source` to `sink` for this configuration. @@ -169,9 +173,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from some source to `sink` for this configuration. */ - predicate hasFlowTo(Node sink) { - sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode() - } + predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) } /** * Holds if data may flow from some source to `sink` for this configuration. @@ -179,10 +181,12 @@ abstract class Configuration extends string { predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } /** + * DEPRECATED: Use `FlowExploration` instead. + * * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` * measured in approximate number of interprocedural steps. */ - int explorationLimit() { none() } + deprecated int explorationLimit() { none() } /** * Holds if hidden nodes should be included in the data flow graph. @@ -191,49 +195,6 @@ abstract class Configuration extends string { * is not visualized (for example in a `path-problem` query). */ predicate includeHiddenNodes() { none() } - - /** - * Holds if there is a partial data flow path from `source` to `node`. The - * approximate distance between `node` and the closest source is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards sink definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sources is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - */ - final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { - partialFlow(source, node, this) and - dist = node.getSourceDistance() - } - - /** - * Holds if there is a partial data flow path from `node` to `sink`. The - * approximate distance between `node` and the closest sink is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards source definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sinks is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - * - * Note that reverse flow has slightly lower precision than the corresponding - * forward flow, as reverse flow disregards type pruning among other features. - */ - final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { - revPartialFlow(node, sink, this) and - dist = node.getSinkDistance() - } } /** @@ -263,90 +224,6 @@ abstract private class ConfigurationRecursionPrevention extends Configuration { } } -private newtype TNodeEx = - TNodeNormal(Node n) or - TNodeImplicitRead(Node n, boolean hasRead) { - any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true] - } - -private class NodeEx extends TNodeEx { - string toString() { - result = this.asNode().toString() - or - exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") - } - - Node asNode() { this = TNodeNormal(result) } - - predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } - - Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } - - pragma[nomagic] - private DataFlowCallable getEnclosingCallable0() { - nodeEnclosingCallable(this.projectToNode(), result) - } - - pragma[inline] - DataFlowCallable getEnclosingCallable() { - pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) - } - - pragma[nomagic] - private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } - - pragma[inline] - DataFlowType getDataFlowType() { - pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) - } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -private class ArgNodeEx extends NodeEx { - ArgNodeEx() { this.asNode() instanceof ArgNode } -} - -private class ParamNodeEx extends NodeEx { - ParamNodeEx() { this.asNode() instanceof ParamNode } - - predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { - this.asNode().(ParamNode).isParameterOf(c, pos) - } - - ParameterPosition getPosition() { this.isParameterOf(_, result) } -} - -private class RetNodeEx extends NodeEx { - RetNodeEx() { this.asNode() instanceof ReturnNodeExt } - - ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } - - ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } -} - -private predicate inBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierIn(n) - | - config.isSource(n) or config.isSource(n, _) - ) -} - -private predicate outBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierOut(n) - | - config.isSink(n) or config.isSink(n, _) - ) -} - /** A bridge class to access the deprecated `isBarrierGuard`. */ private class BarrierGuardGuardedNodeBridge extends Unit { abstract predicate guardedNode(Node n, Configuration config); @@ -370,3019 +247,94 @@ private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { } } -pragma[nomagic] -private predicate fullBarrier(NodeEx node, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n) +private FlowState relevantState(Configuration config) { + config.isSource(_, result) or + config.isSink(_, result) or + config.isBarrier(_, result) or + config.isAdditionalFlowStep(_, result, _, _) or + config.isAdditionalFlowStep(_, _, _, result) +} + +private newtype TConfigState = + TMkConfigState(Configuration config, FlowState state) { + state = relevantState(config) or state instanceof FlowStateEmpty + } + +private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) } + +private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) } + +private predicate singleConfiguration() { 1 = strictcount(Configuration c) } + +private module Config implements FullStateConfigSig { + class FlowState = TConfigState; + + predicate isSource(Node source, FlowState state) { + getConfig(state).isSource(source, getState(state)) or - config.isBarrierIn(n) and - not config.isSource(n) and - not config.isSource(n, _) + getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty + } + + predicate isSink(Node sink, FlowState state) { + getConfig(state).isSink(sink, getState(state)) or - config.isBarrierOut(n) and - not config.isSink(n) and - not config.isSink(n, _) + getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty + } + + predicate isBarrier(Node node) { none() } + + predicate isBarrier(Node node, FlowState state) { + getConfig(state).isBarrier(node, getState(state)) or + getConfig(state).isBarrier(node) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state)) + } + + predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) } + + predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) } + + predicate isAdditionalFlowStep(Node node1, Node node2) { + singleConfiguration() and + any(Configuration config).isAdditionalFlowStep(node1, node2) + } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and + getConfig(state2) = getConfig(state1) or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config) - ) -} - -pragma[nomagic] -private predicate stateBarrier(NodeEx node, FlowState state, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n, state) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config) - ) -} - -pragma[nomagic] -private predicate sourceNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSource(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSource(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -pragma[nomagic] -private predicate sinkNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSink(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSink(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -/** Provides the relevant barriers for a step from `node1` to `node2`. */ -pragma[inline] -private predicate stepFilter(NodeEx node1, NodeEx node2, Configuration config) { - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) -} - -/** - * Holds if data can flow in one local step from `node1` to `node2`. - */ -private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.asNode() = n and - node2.isImplicitReadNode(n, false) and - not fullBarrier(node1, config) - ) -} - -/** - * Holds if the additional step from `node1` to `node2` does not jump between callables. - */ -private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.isImplicitReadNode(n, true) and - node2.asNode() = n and - not fullBarrier(node2, config) - ) -} - -private predicate additionalLocalStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) - ) -} - -/** - * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. - */ -private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if the additional step from `node1` to `node2` jumps between callables. - */ -private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate additionalJumpStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -pragma[nomagic] -private predicate readSet(NodeEx node1, ContentSet c, NodeEx node2, Configuration config) { - readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and - stepFilter(node1, node2, config) - or - exists(Node n | - node2.isImplicitReadNode(n, true) and - node1.isImplicitReadNode(n, _) and - config.allowImplicitRead(n, c) - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(ContentSet cs | - readSet(node1, cs, node2, config) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate clearsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - clearsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate expectsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - expectsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -pragma[nomagic] -private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } - -pragma[nomagic] -private predicate hasReadStep(Content c, Configuration config) { read(_, c, _, config) } - -pragma[nomagic] -private predicate store( - NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config -) { - store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), - contentType) and - hasReadStep(tc.getContent(), config) and - stepFilter(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { - viableReturnPosOut(call, pos, out.asNode()) -} - -pragma[nomagic] -private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { - viableParamArg(call, p.asNode(), arg.asNode()) -} - -/** - * Holds if field flow should be used for the given configuration. - */ -private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } - -private predicate hasSourceCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSourceCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate hasSinkCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSinkCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if flow from `p` to a return node of kind `kind` is allowed. - * - * We don't expect a parameter to return stored in itself, unless - * explicitly allowed - */ -bindingset[p, kind] -private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { - exists(ParameterPosition pos | p.isParameterOf(_, pos) | - not kind.(ParamUpdateReturnKind).getPosition() = pos - or - allowParameterReturnInSelfCached(p.asNode()) - ) -} - -private module Stage1 implements StageSig { - class Ap extends int { - // workaround for bad functionality-induced joins (happens when using `Unit`) - pragma[nomagic] - Ap() { this in [0 .. 1] and this < 1 } - } - - private class Cc = boolean; - - /* Begin: Stage 1 logic. */ - /** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `cc` records whether the node is reached through an - * argument in a call. - */ - private predicate fwdFlow(NodeEx node, Cc cc, Configuration config) { - sourceNode(node, _, config) and - if hasSourceCallCtx(config) then cc = true else cc = false - or - exists(NodeEx mid | fwdFlow(mid, cc, config) | - localFlowStep(mid, node, config) or - additionalLocalFlowStep(mid, node, config) or - additionalLocalStateStep(mid, _, node, _, config) - ) - or - exists(NodeEx mid | fwdFlow(mid, _, config) and cc = false | - jumpStep(mid, node, config) or - additionalJumpStep(mid, node, config) or - additionalJumpStateStep(mid, _, node, _, config) - ) - or - // store - exists(NodeEx mid | - useFieldFlow(config) and - fwdFlow(mid, cc, config) and - store(mid, _, node, _, config) - ) - or - // read - exists(ContentSet c | - fwdFlowReadSet(c, node, cc, config) and - fwdFlowConsCandSet(c, _, config) - ) - or - // flow into a callable - fwdFlowIn(_, _, _, node, config) and - cc = true - or - // flow out of a callable - fwdFlowOut(_, node, false, config) and - cc = false - or - // flow through a callable - exists(DataFlowCall call | - fwdFlowOutFromArg(call, node, config) and - fwdFlowIsEntered(call, cc, config) - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowIn( - DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p, Configuration config - ) { - // call context cannot help reduce virtual dispatch - fwdFlow(arg, cc, config) and - viableParamArgEx(call, p, arg) and - not fullBarrier(p, config) and - ( - cc = false - or - cc = true and - not reducedViableImplInCallContext(call, _, _) - ) - or - // call context may help reduce virtual dispatch - exists(DataFlowCallable target | - fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target, config) and - target = viableImplInSomeFwdFlowCallContextExt(call, config) and - cc = true - ) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - fwdFlowIn(call, _, cc, _, config) - } - - pragma[nomagic] - private predicate fwdFlowInReducedViableImplInSomeCallContext( - DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target, Configuration config - ) { - fwdFlow(arg, true, config) and - viableParamArgEx(call, p, arg) and - reducedViableImplInCallContext(call, _, _) and - target = p.getEnclosingCallable() and - not fullBarrier(p, config) - } - - /** - * Gets a viable dispatch target of `call` in the context `ctx`. This is - * restricted to those `call`s for which a context might make a difference, - * and to `ctx`s that are reachable in `fwdFlow`. - */ - pragma[nomagic] - private DataFlowCallable viableImplInSomeFwdFlowCallContextExt( - DataFlowCall call, Configuration config - ) { - exists(DataFlowCall ctx | - fwdFlowIsEntered(ctx, _, config) and - result = viableImplInCallContextExt(call, ctx) - ) - } - - private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) } - - pragma[nomagic] - private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc, Configuration config) { - exists(NodeEx mid | - fwdFlow(mid, cc, config) and - readSet(mid, c, node, config) - ) - } - - /** - * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - fwdFlow(mid, _, config) and - store(mid, tc, node, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `cs` may be interpreted in a read as the target of some store - * into `c`, in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCandSet(ContentSet cs, Content c, Configuration config) { - fwdFlowConsCand(c, config) and - c = cs.getAReadContent() - } - - pragma[nomagic] - private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { - exists(RetNodeEx ret | - fwdFlow(ret, cc, config) and - ret.getReturnPosition() = pos - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) { - exists(ReturnPosition pos | - fwdFlowReturnPosition(pos, cc, config) and - viableReturnPosOutEx(call, pos, out) and - not fullBarrier(out, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) { - fwdFlowOut(call, out, true, config) - } - - private predicate stateStepFwd(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1 | - additionalLocalStateStep(node1, state1, _, state2, config) or - additionalJumpStateStep(node1, state1, _, state2, config) - | - fwdFlow(node1, config) - ) - } - - private predicate fwdFlowState(FlowState state, Configuration config) { - sourceNode(_, state, config) - or - exists(FlowState state0 | - fwdFlowState(state0, config) and - stateStepFwd(state0, state, config) - ) - } - - /** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink. - */ - pragma[nomagic] - private predicate revFlow(NodeEx node, boolean toReturn, Configuration config) { - revFlow0(node, toReturn, config) and - fwdFlow(node, config) - } - - pragma[nomagic] - private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) { - exists(FlowState state | - fwdFlow(node, pragma[only_bind_into](config)) and - sinkNode(node, state, config) and - fwdFlowState(state, pragma[only_bind_into](config)) and - if hasSinkCallCtx(config) then toReturn = true else toReturn = false - ) - or - exists(NodeEx mid | revFlow(mid, toReturn, config) | - localFlowStep(node, mid, config) or - additionalLocalFlowStep(node, mid, config) or - additionalLocalStateStep(node, _, mid, _, config) - ) - or - exists(NodeEx mid | revFlow(mid, _, config) and toReturn = false | - jumpStep(node, mid, config) or - additionalJumpStep(node, mid, config) or - additionalJumpStateStep(node, _, mid, _, config) - ) - or - // store - exists(Content c | - revFlowStore(c, node, toReturn, config) and - revFlowConsCand(c, config) - ) - or - // read - exists(NodeEx mid, ContentSet c | - readSet(node, c, mid, config) and - fwdFlowConsCandSet(c, _, pragma[only_bind_into](config)) and - revFlow(mid, toReturn, pragma[only_bind_into](config)) - ) - or - // flow into a callable - revFlowIn(_, node, false, config) and - toReturn = false - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(pos, config) and - node.(RetNodeEx).getReturnPosition() = pos and - toReturn = true - ) - or - // flow through a callable - exists(DataFlowCall call | - revFlowInToReturn(call, node, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - /** - * Holds if `c` is the target of a read in the flow covered by `revFlow`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, ContentSet cs | - fwdFlow(node, pragma[only_bind_into](config)) and - readSet(node, cs, mid, config) and - fwdFlowConsCandSet(cs, c, pragma[only_bind_into](config)) and - revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) { - exists(NodeEx mid, TypedContent tc | - revFlow(mid, toReturn, pragma[only_bind_into](config)) and - fwdFlowConsCand(c, pragma[only_bind_into](config)) and - store(node, tc, mid, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `c` is the target of both a read and a store in the flow covered - * by `revFlow`. - */ - pragma[nomagic] - additional predicate revFlowIsReadAndStored(Content c, Configuration conf) { - revFlowConsCand(c, conf) and - revFlowStore(c, _, _, conf) - } - - pragma[nomagic] - additional predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config - ) { - fwdFlowReturnPosition(pos, _, config) and - viableReturnPosOutEx(call, pos, out) - } - - pragma[nomagic] - private predicate revFlowOut(ReturnPosition pos, Configuration config) { - exists(NodeEx out | - revFlow(out, _, config) and - viableReturnPosOutNodeCandFwd1(_, pos, out, config) - ) - } - - pragma[nomagic] - additional predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config - ) { - fwdFlowIn(call, arg, _, p, config) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate revFlowIn( - DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config - ) { - exists(ParamNodeEx p | - revFlow(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) { - revFlowIn(call, arg, true, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(NodeEx out | - revFlow(out, toReturn, config) and - fwdFlowOutFromArg(call, out, config) - ) - } - - private predicate stateStepRev(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - additionalLocalStateStep(node1, state1, node2, state2, config) or - additionalJumpStateStep(node1, state1, node2, state2, config) - | - revFlow(node1, _, pragma[only_bind_into](config)) and - revFlow(node2, _, pragma[only_bind_into](config)) and - fwdFlowState(state1, pragma[only_bind_into](config)) and - fwdFlowState(state2, pragma[only_bind_into](config)) - ) - } - - additional predicate revFlowState(FlowState state, Configuration config) { - exists(NodeEx node | - sinkNode(node, state, config) and - revFlow(node, _, pragma[only_bind_into](config)) and - fwdFlowState(state, pragma[only_bind_into](config)) - ) - or - exists(FlowState state0 | - revFlowState(state0, config) and - stateStepRev(state, state0, config) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Content c | - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - revFlow(node2, pragma[only_bind_into](config)) and - store(node1, tc, node2, contentType, config) and - c = tc.getContent() and - exists(ap1) - ) - } - - pragma[nomagic] - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) { - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - read(n1, c, n2, pragma[only_bind_into](config)) and - revFlow(n2, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, config) and - exists(ap) - } - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, _, pragma[only_bind_into](config)) and - exists(state) and - exists(ap) - } - - private predicate throughFlowNodeCand(NodeEx node, Configuration config) { - revFlow(node, true, config) and - fwdFlow(node, true, config) and - not inBarrier(node, config) and - not outBarrier(node, config) - } - - /** Holds if flow may return from `callable`. */ - pragma[nomagic] - private predicate returnFlowCallableNodeCand( - DataFlowCallable callable, ReturnKindExt kind, Configuration config - ) { - exists(RetNodeEx ret | - throughFlowNodeCand(ret, config) and - callable = ret.getEnclosingCallable() and - kind = ret.getKind() - ) - } - - /** - * Holds if flow may enter through `p` and reach a return node making `p` a - * candidate for the origin of a summary. - */ - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(DataFlowCallable c, ReturnKindExt kind | - throughFlowNodeCand(p, config) and - returnFlowCallableNodeCand(c, kind, config) and - p.getEnclosingCallable() = c and - exists(ap) and - parameterFlowThroughAllowed(p, kind) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - throughFlowNodeCand(ret, config) and - kind = ret.getKind() and - exists(argAp) and - exists(ap) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, boolean toReturn | - revFlow(arg, toReturn, config) and - revFlowInToReturn(call, arg, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, config)) and - fields = count(Content f0 | fwdFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | fwdFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, config)) and - fields = count(Content f0 | revFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | revFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | revFlow(n, b, config)) - } - /* End: Stage 1 logic. */ -} - -pragma[noinline] -private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - localFlowStep(node1, node2, config) -} - -pragma[noinline] -private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - additionalLocalFlowStep(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCand1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config -) { - Stage1::revFlow(out, config) and - Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, Configuration config -) { - exists(ReturnPosition pos | - viableReturnPosOutNodeCand1(call, pos, out, config) and - pos = ret.getReturnPosition() and - kind = pos.getKind() and - Stage1::revFlow(ret, config) and - not outBarrier(ret, config) and - not inBarrier(out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCand1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config -) { - Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and - Stage1::revFlow(arg, config) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config -) { - viableParamArgNodeCand1(call, p, arg, config) and - Stage1::revFlow(p, config) and - not outBarrier(arg, config) and - not inBarrier(p, config) -} - -/** - * Gets the amount of forward branching on the origin of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int branch(NodeEx n1, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n1, _, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) - ) -} - -/** - * Gets the amount of backward branching on the target of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int join(NodeEx n2, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n, _, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) - ) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. The - * `allowsFieldFlow` flag indicates whether the branching is within the limit - * specified by the configuration. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, ret, kind, out, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(ret, pragma[only_bind_into](config)) and - j = join(out, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. The `allowsFieldFlow` flag indicates whether - * the branching is within the limit specified by the configuration. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config -) { - flowIntoCallNodeCand1(call, arg, p, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(arg, pragma[only_bind_into](config)) and - j = join(p, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -private signature module StageSig { - class Ap; - - predicate revFlow(NodeEx node, Configuration config); - - predicate revFlowAp(NodeEx node, Ap ap, Configuration config); - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config); - - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config); - - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config); - - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ); - - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ); - - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config); -} - -private module MkStage { - class ApApprox = PrevStage::Ap; - - signature module StageParam { - class Ap; - - class ApNil extends Ap; - - bindingset[result, ap] - ApApprox getApprox(Ap ap); - - ApNil getApNil(NodeEx node); - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail); - - /** - * An approximation of `Content` that corresponds to the precision level of - * `Ap`, such that the mappings from both `Ap` and `Content` to this type - * are functional. - */ - class ApHeadContent; - - ApHeadContent getHeadContent(Ap ap); - - ApHeadContent projectToHeadContent(Content c); - - class ApOption; - - ApOption apNone(); - - ApOption apSome(Ap ap); - - class Cc; - - class CcCall extends Cc; - - // TODO: member predicate on CcCall - predicate matchesCall(CcCall cc, DataFlowCall call); - - class CcNoCall extends Cc; - - Cc ccNone(); - - CcCall ccSomeCall(); - - class LocalCc; - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc); - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ); - - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config - ); - - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config - ); - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config); - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType); - } - - module Stage implements StageSig { - import Param - - /* Begin: Stage logic. */ - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - private predicate revFlowApAlias(NodeEx node, ApApprox apa, Configuration config) { - PrevStage::revFlowAp(node, apa, config) - } - - pragma[nomagic] - private predicate flowIntoCallApa( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa, - Configuration config - ) { - flowIntoCall(call, arg, p, allowsFieldFlow, config) and - PrevStage::revFlowAp(p, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(arg, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowOutOfCallApa( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - ApApprox apa, Configuration config - ) { - flowOutOfCall(call, ret, kind, out, allowsFieldFlow, config) and - PrevStage::revFlowAp(out, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(ret, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowThroughOutOfCall( - DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, - ApApprox argApa, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa, pragma[only_bind_into](config)) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) and - matchesCall(ccc, call) - ) - } - - /** - * Holds if `node` is reachable with access path `ap` from a source in the - * configuration `config`. - * - * The call context `cc` records whether the node is reached through an - * argument in a call, and if so, `summaryCtx` and `argAp` record the - * corresponding parameter position and access path of that argument, respectively. - */ - pragma[nomagic] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa, config) and - PrevStage::revFlow(node, state, apa, config) and - filter(node, state, ap, config) - } - - pragma[inline] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, cc, summaryCtx, argAp, ap, _, config) - } - - pragma[nomagic] - private predicate fwdFlow0( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - sourceNode(node, state, config) and - (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and - argAp = apNone() and - summaryCtx = TParamNodeNone() and - ap = getApNil(node) and - apa = getApprox(ap) - or - exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | - fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0, config) and - localCc = getLocalCc(mid, cc) - | - localStep(mid, state0, node, state, true, _, config, localCc) and - ap = ap0 and - apa = apa0 - or - localStep(mid, state0, node, state, false, ap, config, localCc) and - ap0 instanceof ApNil and - apa = getApprox(ap) - ) - or - exists(NodeEx mid | - fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa, pragma[only_bind_into](config)) and - jumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(mid, state, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(mid, state0, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStateStep(mid, state0, node, state, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - // store - exists(TypedContent tc, Ap ap0 | - fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp, config) and - ap = apCons(tc, ap0) and - apa = getApprox(ap) - ) - or - // read - exists(Ap ap0, Content c | - fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp, config) and - fwdFlowConsCand(ap0, c, ap, config) and - apa = getApprox(ap) - ) - or - // flow into a callable - fwdFlowIn(_, node, state, _, cc, _, _, ap, apa, config) and - if PrevStage::parameterMayFlowThrough(node, apa, config) - then ( - summaryCtx = TParamNodeSome(node.asNode()) and - argAp = apSome(ap) - ) else ( - summaryCtx = TParamNodeNone() and argAp = apNone() - ) - or - // flow out of a callable - exists( - DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, - DataFlowCallable inner - | - fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa, config) and - flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa, config) and - inner = ret.getEnclosingCallable() and - cc = getCallContextReturn(inner, call, innercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - or - // flow through a callable - exists( - DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, ApApprox innerArgApa - | - fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowStore( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(DataFlowType contentType, ApApprox apa1 | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1, config) and - PrevStage::storeStepCand(node1, apa1, tc, node2, contentType, config) and - typecheckStore(ap1, contentType) - ) - } - - /** - * Holds if forward flow with access path `tail` reaches a store of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(TypedContent tc | - fwdFlowStore(_, tail, tc, _, _, _, _, _, config) and - tc.getContent() = c and - cons = apCons(tc, tail) - ) - } - - pragma[nomagic] - private predicate readStepCand( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - PrevStage::readStepCand(node1, c, node2, config) and - apc = projectToHeadContent(c) - } - - bindingset[node1, apc] - pragma[inline_late] - private predicate readStepCand0( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - readStepCand(node1, apc, c, node2, config) - } - - pragma[nomagic] - private predicate fwdFlowRead( - Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(ApHeadContent apc | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap, config) and - apc = getHeadContent(ap) and - readStepCand0(node1, apc, c, node2, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowIn( - DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, - ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa, Configuration config - ) { - exists(ArgNodeEx arg, boolean allowsFieldFlow | - fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa, config) and - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowRetFromArg( - RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, ApApprox argApa, - Ap ap, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - fwdFlow(pragma[only_bind_into](ret), state, ccc, - TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), - pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa), - pragma[only_bind_into](config)) and - kind = ret.getKind() and - parameterFlowThroughAllowed(summaryCtx, kind) and - argApa = getApprox(argAp) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) - ) - } - - pragma[inline] - private predicate fwdFlowThrough0( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, - Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa, config) and - fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp, config) - } - - pragma[nomagic] - private predicate fwdFlowThrough( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa, - config) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` - * and data might flow through the target callable and back out at `call`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered( - DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, - ParamNodeEx p, Ap ap, Configuration config - ) { - exists(ApApprox apa | - fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(p, apa, config) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate storeStepFwd( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config - ) { - fwdFlowStore(node1, ap1, tc, node2, _, _, _, _, config) and - ap2 = apCons(tc, ap1) and - fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _, config) - } - - private predicate readStepFwd( - NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config - ) { - fwdFlowRead(ap1, c, n1, n2, _, _, _, _, config) and - fwdFlowConsCand(ap1, c, ap2, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough0( - DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, - ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, - innerArgApa, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough( - RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, - Ap ap, Configuration config - ) { - exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | - returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa, config) and - pos = ret.getReturnPosition() and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowThroughIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap, - Configuration config - ) { - exists(ApApprox argApa | - flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), - allowsFieldFlow, argApa, pragma[only_bind_into](config)) and - fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa, - pragma[only_bind_into](config)) and - returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap, - pragma[only_bind_into](config)) and - if allowsFieldFlow = false then argAp instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowIntoCallAp( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap, - Configuration config - ) { - exists(ApApprox apa | - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - fwdFlow(arg, _, _, _, _, ap, apa, config) - ) - } - - pragma[nomagic] - private predicate flowOutOfCallAp( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, - Ap ap, Configuration config - ) { - exists(ApApprox apa | - flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa, config) and - fwdFlow(ret, _, _, _, _, ap, apa, config) and - pos = ret.getReturnPosition() - ) - } - - /** - * Holds if `node` with access path `ap` is part of a path from a source to a - * sink in the configuration `config`. - * - * The parameter `returnCtx` records whether (and how) the node must be returned - * from the enclosing callable in order to reach a sink, and if so, `returnAp` - * records the access path of the returned value. - */ - pragma[nomagic] - additional predicate revFlow( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - revFlow0(node, state, returnCtx, returnAp, ap, config) and - fwdFlow(node, state, _, _, _, ap, config) - } - - pragma[nomagic] - private predicate revFlow0( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, _, _, _, ap, config) and - sinkNode(node, state, config) and - ( - if hasSinkCallCtx(config) - then returnCtx = TReturnCtxNoFlowThrough() - else returnCtx = TReturnCtxNone() - ) and - returnAp = apNone() and - ap instanceof ApNil - or - exists(NodeEx mid, FlowState state0 | - localStep(node, state, mid, state0, true, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, ap, config) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap, pragma[only_bind_into](config)) and - localStep(node, pragma[only_bind_into](state), mid, state0, false, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, nil, pragma[only_bind_into](config)) and - ap instanceof ApNil - ) - or - exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, state, _, _, ap, config) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStep(node, mid, config) and - revFlow(pragma[only_bind_into](mid), state, _, _, nil, pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStateStep(node, state, mid, state0, config) and - revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil, - pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - // store - exists(Ap ap0, Content c | - revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp, config) and - revFlowConsCand(ap0, c, ap, config) - ) - or - // read - exists(NodeEx mid, Ap ap0 | - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - readStepFwd(node, ap, _, mid, ap0, config) - ) - or - // flow into a callable - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, state, TReturnCtxNone(), returnAp, ap, config) and - flowIntoCallAp(_, node, p, allowsFieldFlow, ap, config) and - (if allowsFieldFlow = false then ap instanceof ApNil else any()) and - returnCtx = TReturnCtxNone() - ) - or - // flow through a callable - exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, node, p, _, ap, innerReturnAp, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(_, node, pos, state, _, _, ap, config) and - if returnFlowsThrough(node, pos, state, _, _, _, ap, config) - then ( - returnCtx = TReturnCtxMaybeFlowThrough(pos) and - returnAp = apSome(ap) - ) else ( - returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() - ) - ) - } - - pragma[nomagic] - private predicate revFlowStore( - Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, - ReturnCtx returnCtx, ApOption returnAp, Configuration config - ) { - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - storeStepFwd(node, ap, tc, mid, ap0, config) and - tc.getContent() = c - } - - /** - * Holds if reverse flow with access path `tail` reaches a read of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(NodeEx mid, Ap tail0 | - revFlow(mid, _, _, _, tail, config) and - tail = pragma[only_bind_into](tail0) and - readStepFwd(_, cons, c, mid, tail0, config) - ) - } - - pragma[nomagic] - private predicate revFlowOut( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, - ApOption returnAp, Ap ap, Configuration config - ) { - exists(NodeEx out, boolean allowsFieldFlow | - revFlow(out, state, returnCtx, returnAp, ap, config) and - flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate revFlowParamToReturn( - ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap, Configuration config - ) { - revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), - pragma[only_bind_into](ap), pragma[only_bind_into](config)) and - parameterFlowThroughAllowed(p, pos.getKind()) and - PrevStage::parameterMayFlowThrough(p, getApprox(ap), config) - } - - pragma[nomagic] - private predicate revFlowThrough( - DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, - ApOption returnAp, Ap ap, Ap innerReturnAp, Configuration config - ) { - revFlowParamToReturn(p, state, pos, innerReturnAp, ap, config) and - revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned( - DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap, - Configuration config - ) { - exists(RetNodeEx ret, FlowState state, CcCall ccc | - revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap, config) and - returnFlowsThrough(ret, pos, state, ccc, _, _, ap, config) and - matchesCall(ccc, call) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Ap ap2, Content c | - PrevStage::storeStepCand(node1, _, tc, node2, contentType, config) and - revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _, config) and - revFlowConsCand(ap2, c, ap1, config) - ) - } - - predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(Ap ap1, Ap ap2 | - revFlow(node2, _, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and - readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _, - pragma[only_bind_into](config)) - ) - } - - additional predicate revFlow(NodeEx node, FlowState state, Configuration config) { - revFlow(node, state, _, _, _, config) - } - - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, _, _, ap, config) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, _, _, _, ap, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, Configuration config) { - revFlow(node, _, _, _, _, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, ap, config) - } - - private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepFwd(_, ap, tc, _, _, config) - } - - private predicate revConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepCand(_, ap, tc, _, _, config) - } - - private predicate validAp(Ap ap, Configuration config) { - revFlow(_, _, _, _, ap, config) and ap instanceof ApNil - or - exists(TypedContent head, Ap tail | - consCand(head, tail, config) and - ap = apCons(head, tail) - ) - } - - additional predicate consCand(TypedContent tc, Ap ap, Configuration config) { - revConsCand(tc, ap, config) and - validAp(ap, config) - } - - pragma[nomagic] - private predicate parameterFlowsThroughRev( - ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp, Configuration config - ) { - revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap, config) and - parameterFlowThroughAllowed(p, pos.getKind()) - } - - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(ReturnPosition pos | - returnFlowsThrough(_, pos, _, _, p, ap, _, config) and - parameterFlowsThroughRev(p, ap, pos, _, config) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - exists(ParamNodeEx p, ReturnPosition pos | - returnFlowsThrough(ret, pos, _, _, p, argAp, ap, config) and - parameterFlowsThroughRev(p, argAp, pos, ap, config) and - kind = pos.getKind() - ) - } - - pragma[nomagic] - private predicate revFlowThroughArg( - DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, - Ap ap, Configuration config - ) { - exists(ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp, config) - ) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | - revFlow(arg, state, returnCtx, returnAp, ap, config) and - revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, config)) and - fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and - states = count(FlowState state | fwdFlow(_, state, _, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | - fwdFlow(n, state, cc, summaryCtx, argAp, ap, config) - ) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, _, _, _, config)) and - fields = count(TypedContent f0 | consCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and - states = count(FlowState state | revFlow(_, state, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | - revFlow(n, state, returnCtx, retAp, ap, config) - ) - } - /* End: Stage logic. */ - } -} - -private module BooleanCallContext { - class Cc extends boolean { - Cc() { this in [true, false] } - } - - class CcCall extends Cc { - CcCall() { this = true } - } - - /** Holds if the call context may be `call`. */ - predicate matchesCall(CcCall cc, DataFlowCall call) { any() } - - class CcNoCall extends Cc { - CcNoCall() { this = false } - } - - Cc ccNone() { result = false } - - CcCall ccSomeCall() { result = true } - - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } -} - -private module Level1CallContext { - class Cc = CallContext; - - class CcCall = CallContextCall; - - pragma[inline] - predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } - - class CcNoCall = CallContextNoCall; - - Cc ccNone() { result instanceof CallContextAny } - - CcCall ccSomeCall() { result instanceof CallContextSomeCall } - - module NoLocalCallContext { - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSiteDispatch(call, c) - then result = TSpecificCall(call) - else result = TSomeCall() - } - } - - module LocalCallContext { - class LocalCc = LocalCallContext; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { - result = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - node.getEnclosingCallable()) - } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() - } - } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { - checkCallContextReturn(innercc, c, call) and - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() - } -} - -private module Stage2Param implements MkStage::StageParam { - private module PrevStage = Stage1; - - class Ap extends boolean { - Ap() { this in [true, false] } - } - - class ApNil extends Ap { - ApNil() { this = false } - } - - bindingset[result, ap] - PrevStage::Ap getApprox(Ap ap) { any() } - - ApNil getApNil(NodeEx node) { Stage1::revFlow(node, _) and exists(result) } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } - - class ApHeadContent = Unit; - - pragma[inline] - ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } - - ApHeadContent projectToHeadContent(Content c) { any() } - - class ApOption = BooleanOption; - - ApOption apNone() { result = TBooleanNone() } - - ApOption apSome(Ap ap) { result = TBooleanSome(ap) } - - import Level1CallContext - import NoLocalCallContext - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - ( - preservesValue = true and - localFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalStateStep(node1, state1, node2, state2, config) - ) and - exists(ap) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand1/6; - - predicate flowIntoCall = flowIntoCallNodeCand1/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - expectsContentEx(node, c) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - PrevStage::revFlowState(state, pragma[only_bind_into](config)) and - exists(ap) and - not stateBarrier(node, state, config) and - ( - notExpectsContent(node) - or - ap = true and - expectsContentCand(node, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage2 implements StageSig { - import MkStage::Stage -} - -pragma[nomagic] -private predicate flowOutOfCallNodeCand2( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -pragma[nomagic] -private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -private module LocalFlowBigStep { - /** - * A node where some checking is required, and hence the big-step relation - * is not allowed to step over. - */ - private class FlowCheckNode extends NodeEx { - FlowCheckNode() { - castNode(this.asNode()) or - clearsContentCached(this.asNode(), _) or - expectsContentCached(this.asNode(), _) - } - } - - /** - * Holds if `node` can be the first node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowEntry(NodeEx node, FlowState state, Configuration config) { - Stage2::revFlow(node, state, config) and - ( - sourceNode(node, state, config) - or - jumpStep(_, node, config) - or - additionalJumpStep(_, node, config) - or - additionalJumpStateStep(_, _, node, state, config) - or - node instanceof ParamNodeEx - or - node.asNode() instanceof OutNodeExt - or - Stage2::storeStepCand(_, _, _, node, _, config) - or - Stage2::readStepCand(_, _, node, config) - or - node instanceof FlowCheckNode - or - exists(FlowState s | - additionalLocalStateStep(_, s, node, state, config) and - s != state - ) - ) - } - - /** - * Holds if `node` can be the last node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowExit(NodeEx node, FlowState state, Configuration config) { - exists(NodeEx next | Stage2::revFlow(next, state, config) | - jumpStep(node, next, config) or - additionalJumpStep(node, next, config) or - flowIntoCallNodeCand2(_, node, next, _, config) or - flowOutOfCallNodeCand2(_, node, _, next, _, config) or - Stage2::storeStepCand(node, _, _, next, _, config) or - Stage2::readStepCand(node, _, next, config) - ) - or - exists(NodeEx next, FlowState s | Stage2::revFlow(next, s, config) | - additionalJumpStateStep(node, state, next, s, config) - or - additionalLocalStateStep(node, state, next, s, config) and - s != state - ) - or - Stage2::revFlow(node, state, config) and - node instanceof FlowCheckNode - or - sinkNode(node, state, config) - } - - pragma[noinline] - private predicate additionalLocalFlowStepNodeCand2( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, Configuration config - ) { - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 and - Stage2::revFlow(node1, pragma[only_bind_into](state1), false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false, - pragma[only_bind_into](config)) - or - additionalLocalStateStep(node1, state1, node2, state2, config) and - Stage2::revFlow(node1, state1, false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, state2, false, pragma[only_bind_into](config)) - } - - /** - * Holds if the local path from `node1` to `node2` is a prefix of a maximal - * subsequence of local flow steps in a dataflow path. - * - * This is the transitive closure of `[additional]localFlowStep` beginning - * at `localFlowEntry`. - */ - pragma[nomagic] - private predicate localFlowStepPlus( - NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, - Configuration config, LocalCallContext cc - ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and - ( - localFlowEntry(node1, pragma[only_bind_into](state), pragma[only_bind_into](config)) and - ( - localFlowStepNodeCand1(node1, node2, config) and - preservesValue = true and - t = node1.getDataFlowType() and // irrelevant dummy value - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - or - additionalLocalFlowStepNodeCand2(node1, state, node2, state, config) and - preservesValue = false and - t = node2.getDataFlowType() - ) and - node1 != node2 and - cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) - or - exists(NodeEx mid | - localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, - pragma[only_bind_into](config), cc) and - localFlowStepNodeCand1(mid, node2, config) and - not mid instanceof FlowCheckNode and - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - ) - or - exists(NodeEx mid | - localFlowStepPlus(node1, state, mid, _, _, pragma[only_bind_into](config), cc) and - additionalLocalFlowStepNodeCand2(mid, state, node2, state, config) and - not mid instanceof FlowCheckNode and - preservesValue = false and - t = node2.getDataFlowType() - ) - ) - } - - /** - * Holds if `node1` can step to `node2` in one or more local steps and this - * path can occur as a maximal subsequence of local steps in a dataflow path. - */ - pragma[nomagic] - predicate localFlowBigStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - DataFlowType t, Configuration config, LocalCallContext callContext - ) { - localFlowStepPlus(node1, state1, node2, preservesValue, t, config, callContext) and - localFlowExit(node2, state1, config) and - state1 = state2 - or - additionalLocalFlowStepNodeCand2(node1, state1, node2, state2, config) and - state1 != state2 and - preservesValue = false and - t = node2.getDataFlowType() and - callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) - } -} - -private import LocalFlowBigStep - -private module Stage3Param implements MkStage::StageParam { - private module PrevStage = Stage2; - - class Ap = ApproxAccessPathFront; - - class ApNil = ApproxAccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } - - class ApHeadContent = ContentApprox; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - predicate projectToHeadContent = getContentApprox/1; - - class ApOption = ApproxAccessPathFrontOption; - - ApOption apNone() { result = TApproxAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } - - import BooleanCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApproxAccessPathFrontNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand2/6; - - predicate flowIntoCall = flowIntoCallNodeCand2/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getAHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage3 implements StageSig { - import MkStage::Stage -} - -private module Stage4Param implements MkStage::StageParam { - private module PrevStage = Stage3; - - class Ap = AccessPathFront; - - class ApNil = AccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathFrontOption; - - ApOption apNone() { result = TAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } - - import BooleanCallContext - - pragma[nomagic] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) and - exists(lcc) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clearSet(NodeEx node, ContentSet c, Configuration config) { - PrevStage::revFlow(node, config) and - clearsContentCached(node.asNode(), c) - } - - pragma[nomagic] - private predicate clearContent(NodeEx node, Content c, Configuration config) { - exists(ContentSet cs | - PrevStage::readStepCand(_, pragma[only_bind_into](c), _, pragma[only_bind_into](config)) and - c = cs.getAReadContent() and - clearSet(node, cs, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clear(NodeEx node, Ap ap, Configuration config) { - clearContent(node, ap.getHead().getContent(), config) - } - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - not clear(node, ap, config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage4 implements StageSig { - import MkStage::Stage -} - -/** - * Holds if `argApf` is recorded as the summary context for flow reaching `node` - * and remains relevant for the following pruning stage. - */ -private predicate flowCandSummaryCtx( - NodeEx node, FlowState state, AccessPathFront argApf, Configuration config -) { - exists(AccessPathFront apf | - Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf, config) and - Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf, - config) - ) -} - -/** - * Holds if a length 2 access path approximation with the head `tc` is expected - * to be expensive. - */ -private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { - exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf, config)) and - nodes = - strictcount(NodeEx n, FlowState state | - Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - or - flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - ) and - accessPathApproxCostLimits(apLimit, tupleLimit) and - apLimit < tails and - tupleLimit < (tails - 1) * nodes and - not tc.forceHighPrecision() - ) -} - -private newtype TAccessPathApprox = - TNil(DataFlowType t) or - TConsNil(TypedContent tc, DataFlowType t) { - Stage4::consCand(tc, TFrontNil(t), _) and - not expensiveLen2unfolding(tc, _) - } or - TConsCons(TypedContent tc1, TypedContent tc2, int len) { - Stage4::consCand(tc1, TFrontHead(tc2), _) and - len in [2 .. accessPathLimit()] and - not expensiveLen2unfolding(tc1, _) - } or - TCons1(TypedContent tc, int len) { - len in [1 .. accessPathLimit()] and - expensiveLen2unfolding(tc, _) - } - -/** - * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only - * the first two elements of the list and its length are tracked. If data flows - * from a source to a given node with a given `AccessPathApprox`, this indicates - * the sequence of dereference operations needed to get from the value in the node - * to the tracked object. The final type indicates the type of the tracked object. - */ -abstract private class AccessPathApprox extends TAccessPathApprox { - abstract string toString(); - - abstract TypedContent getHead(); - - abstract int len(); - - abstract DataFlowType getType(); - - abstract AccessPathFront getFront(); - - /** Gets the access path obtained by popping `head` from this path, if any. */ - abstract AccessPathApprox pop(TypedContent head); -} - -private class AccessPathApproxNil extends AccessPathApprox, TNil { - private DataFlowType t; - - AccessPathApproxNil() { this = TNil(t) } - - override string toString() { result = concat(": " + ppReprType(t)) } - - override TypedContent getHead() { none() } - - override int len() { result = 0 } - - override DataFlowType getType() { result = t } - - override AccessPathFront getFront() { result = TFrontNil(t) } - - override AccessPathApprox pop(TypedContent head) { none() } -} - -abstract private class AccessPathApproxCons extends AccessPathApprox { } - -private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { - private TypedContent tc; - private DataFlowType t; - - AccessPathApproxConsNil() { this = TConsNil(tc, t) } - - override string toString() { - // The `concat` becomes "" if `ppReprType` has no result. - result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) - } - - override TypedContent getHead() { result = tc } - - override int len() { result = 1 } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } -} - -private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { - private TypedContent tc1; - private TypedContent tc2; - private int len; - - AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - - override string toString() { - if len = 2 - then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" - else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc1 } - - override int len() { result = len } - - override DataFlowType getType() { result = tc1.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc1) } - - override AccessPathApprox pop(TypedContent head) { - head = tc1 and - ( - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - } -} - -private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { - private TypedContent tc; - private int len; - - AccessPathApproxCons1() { this = TCons1(tc, len) } - - override string toString() { - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc } - - override int len() { result = len } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { - head = tc and - ( - exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2), _) | - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - or - exists(DataFlowType t | - len = 1 and - Stage4::consCand(tc, TFrontNil(t), _) and - result = TNil(t) - ) - ) - } -} - -/** Gets the access path obtained by popping `tc` from `ap`, if any. */ -private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } - -/** Gets the access path obtained by pushing `tc` onto `ap`. */ -private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } - -private newtype TAccessPathApproxOption = - TAccessPathApproxNone() or - TAccessPathApproxSome(AccessPathApprox apa) - -private class AccessPathApproxOption extends TAccessPathApproxOption { - string toString() { - this = TAccessPathApproxNone() and result = "" - or - this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) - } -} - -private module Stage5Param implements MkStage::StageParam { - private module PrevStage = Stage4; - - class Ap = AccessPathApprox; - - class ApNil = AccessPathApproxNil; - - pragma[nomagic] - PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathApproxOption; - - ApOption apNone() { result = TAccessPathApproxNone() } - - ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } - - import Level1CallContext - import LocalCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, lcc) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { any() } - - // Type checking is not necessary here as it has already been done in stage 3. - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage5 = MkStage::Stage; - -bindingset[conf, result] -private Configuration unbindConf(Configuration conf) { - exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c)) -} - -pragma[nomagic] -private predicate nodeMayUseSummary0( - NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(AccessPathApprox apa0 | - Stage5::parameterMayFlowThrough(p, _, _) and - Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0, config) and - Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), - TAccessPathApproxSome(apa), apa0, config) - ) -} - -pragma[nomagic] -private predicate nodeMayUseSummary( - NodeEx n, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::parameterMayFlowThrough(p, apa, config) and - nodeMayUseSummary0(n, p, state, apa, config) - ) -} - -private newtype TSummaryCtx = - TSummaryCtxNone() or - TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { - exists(Configuration config | - Stage5::parameterMayFlowThrough(p, ap.getApprox(), config) and - Stage5::revFlow(p, state, _, config) - ) - } - -/** - * A context for generating flow summaries. This represents flow entry through - * a specific parameter with an access path of a specific shape. - * - * Summaries are only created for parameters that may flow through. - */ -abstract private class SummaryCtx extends TSummaryCtx { - abstract string toString(); -} - -/** A summary context from which no flow summary can be generated. */ -private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { - override string toString() { result = "" } -} - -/** A summary context from which a flow summary can be generated. */ -private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParamNodeEx p; - private FlowState s; - private AccessPath ap; - - SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } - - ParameterPosition getParameterPos() { p.isParameterOf(_, result) } - - ParamNodeEx getParamNode() { result = p } - - override string toString() { result = p + ": " + ap } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** - * Gets the number of length 2 access path approximations that correspond to `apa`. - */ -private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { - exists(TypedContent tc, int len | - tc = apa.getHead() and - len = apa.len() and - result = - strictcount(AccessPathFront apf | - Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), - config) - ) - ) -} - -private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = - strictcount(NodeEx n, FlowState state | - Stage5::revFlow(n, state, apa, config) or nodeMayUseSummary(n, state, apa, config) - ) -} - -/** - * Holds if a length 2 access path approximation matching `apa` is expected - * to be expensive. - */ -private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = count1to2unfold(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - apLimit < aps and - tupleLimit < (aps - 1) * nodes - ) -} - -private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { - exists(TypedContent head | - apa.pop(head) = result and - Stage5::consCand(head, result, config) - ) -} - -/** - * Holds with `unfold = false` if a precise head-tail representation of `apa` is - * expected to be expensive. Holds with `unfold = true` otherwise. - */ -private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { - if apa.getHead().forceHighPrecision() - then unfold = true - else - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = countPotentialAps(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true - ) -} - -/** - * Gets the number of `AccessPath`s that correspond to `apa`. - */ -pragma[assume_small_delta] -private int countAps(AccessPathApprox apa, Configuration config) { - evalUnfold(apa, false, config) and - result = 1 and - (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) - or - evalUnfold(apa, false, config) and - result = count1to2unfold(apa, config) and - not expensiveLen1to2unfolding(apa, config) - or - evalUnfold(apa, true, config) and - result = countPotentialAps(apa, config) -} - -/** - * Gets the number of `AccessPath`s that would correspond to `apa` assuming - * that it is expanded to a precise head-tail representation. - */ -language[monotonicAggregates] -pragma[assume_small_delta] -private int countPotentialAps(AccessPathApprox apa, Configuration config) { - apa instanceof AccessPathApproxNil and result = 1 - or - result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) -} - -private newtype TAccessPath = - TAccessPathNil(DataFlowType t) or - TAccessPathCons(TypedContent head, AccessPath tail) { - exists(AccessPathApproxCons apa | - not evalUnfold(apa, false, _) and - head = apa.getHead() and - tail.getApprox() = getATail(apa, _) - ) - } or - TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - not expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head1 = apa.getHead() and - head2 = getATail(apa, _).getHead() - ) - } or - TAccessPathCons1(TypedContent head, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head = apa.getHead() - ) - } - -private newtype TPathNode = - pragma[assume_small_delta] - TPathNodeMid( - NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config - ) { - // A PathNode is introduced by a source ... - Stage5::revFlow(node, state, config) and - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - or - // ... or a step from an existing PathNode to another node. - exists(PathNodeMid mid | - pathStep(mid, node, state, cc, sc, ap) and - pragma[only_bind_into](config) = mid.getConfiguration() and - Stage5::revFlow(node, state, ap.getApprox(), pragma[only_bind_into](config)) - ) - } or - TPathNodeSink(NodeEx node, FlowState state, Configuration config) { - exists(PathNodeMid sink | - sink.isAtSink() and - node = sink.getNodeEx() and - state = sink.getState() and - config = sink.getConfiguration() - ) - } or - TPathNodeSourceGroup(string sourceGroup, Configuration config) { - exists(PathNodeImpl source | - sourceGroup = source.getSourceGroup() and - config = source.getConfiguration() - ) - } or - TPathNodeSinkGroup(string sinkGroup, Configuration config) { - exists(PathNodeSink sink | - sinkGroup = sink.getSinkGroup() and - config = sink.getConfiguration() - ) - } - -/** - * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a - * source to a given node with a given `AccessPath`, this indicates the sequence - * of dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ -private class AccessPath extends TAccessPath { - /** Gets the head of this access path, if any. */ - abstract TypedContent getHead(); - - /** Gets the tail of this access path, if any. */ - abstract AccessPath getTail(); - - /** Gets the front of this access path. */ - abstract AccessPathFront getFront(); - - /** Gets the approximation of this access path. */ - abstract AccessPathApprox getApprox(); - - /** Gets the length of this access path. */ - abstract int length(); - - /** Gets a textual representation of this access path. */ - abstract string toString(); - - /** Gets the access path obtained by popping `tc` from this access path, if any. */ - final AccessPath pop(TypedContent tc) { - result = this.getTail() and - tc = this.getHead() - } - - /** Gets the access path obtained by pushing `tc` onto this access path. */ - final AccessPath push(TypedContent tc) { this = result.pop(tc) } -} - -private class AccessPathNil extends AccessPath, TAccessPathNil { - private DataFlowType t; - - AccessPathNil() { this = TAccessPathNil(t) } - - DataFlowType getType() { result = t } - - override TypedContent getHead() { none() } - - override AccessPath getTail() { none() } - - override AccessPathFrontNil getFront() { result = TFrontNil(t) } - - override AccessPathApproxNil getApprox() { result = TNil(t) } - - override int length() { result = 0 } - - override string toString() { result = concat(": " + ppReprType(t)) } -} - -private class AccessPathCons extends AccessPath, TAccessPathCons { - private TypedContent head; - private AccessPath tail; - - AccessPathCons() { this = TAccessPathCons(head, tail) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { result = tail } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - pragma[assume_small_delta] - override AccessPathApproxCons getApprox() { - result = TConsNil(head, tail.(AccessPathNil).getType()) - or - result = TConsCons(head, tail.getHead(), this.length()) - or - result = TCons1(head, this.length()) - } - - pragma[assume_small_delta] - override int length() { result = 1 + tail.length() } - - private string toStringImpl(boolean needsSuffix) { - exists(DataFlowType t | - tail = TAccessPathNil(t) and - needsSuffix = false and - result = head.toString() + "]" + concat(" : " + ppReprType(t)) - ) - or - result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) - or - exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | - result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true - or - result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false - ) - or - exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | - result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true - or - result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false - ) - } - - override string toString() { - result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" - or - result = "[" + this.toStringImpl(false) - } -} - -private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { - private TypedContent head1; - private TypedContent head2; - private int len; - - AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } - - override TypedContent getHead() { result = head1 } - - override AccessPath getTail() { - Stage5::consCand(head1, result.getApprox(), _) and - result.getHead() = head2 and - result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head1) } - - override AccessPathApproxCons getApprox() { - result = TConsCons(head1, head2, len) or - result = TCons1(head1, len) - } - - override int length() { result = len } - - override string toString() { - if len = 2 - then result = "[" + head1.toString() + ", " + head2.toString() + "]" - else - result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" - } -} - -private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { - private TypedContent head; - private int len; - - AccessPathCons1() { this = TAccessPathCons1(head, len) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { - Stage5::consCand(head, result.getApprox(), _) and result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - - override int length() { result = len } - - override string toString() { - if len = 1 - then result = "[" + head.toString() + "]" - else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" - } -} - -abstract private class PathNodeImpl extends TPathNode { - /** Gets the `FlowState` of this node. */ - abstract FlowState getState(); - - /** Gets the associated configuration. */ - abstract Configuration getConfiguration(); - - /** Holds if this node is a source. */ - abstract predicate isSource(); - - abstract PathNodeImpl getASuccessorImpl(); - - private PathNodeImpl getASuccessorIfHidden() { - this.isHidden() and - result = this.getASuccessorImpl() - } - - pragma[nomagic] - private PathNodeImpl getANonHiddenSuccessor0() { - result = this.getASuccessorIfHidden*() and - not result.isHidden() - } - - final PathNodeImpl getANonHiddenSuccessor() { - result = this.getASuccessorImpl().getANonHiddenSuccessor0() and - not this.isHidden() - } - - abstract NodeEx getNodeEx(); - - predicate isHidden() { - not this.getConfiguration().includeHiddenNodes() and - ( - hiddenNode(this.getNodeEx().asNode()) and - not this.isSource() and - not this instanceof PathNodeSink - or - this.getNodeEx() instanceof TNodeImplicitRead - ) - } - - string getSourceGroup() { - this.isSource() and - this.getConfiguration().sourceGrouping(this.getNodeEx().asNode(), result) + not singleConfiguration() and + getConfig(state1).isAdditionalFlowStep(node1, node2) and + state2 = state1 } - predicate isFlowSource() { - this.isSource() and not exists(this.getSourceGroup()) - or - this instanceof PathNodeSourceGroup + predicate allowImplicitRead(Node node, ContentSet c) { + any(Configuration config).allowImplicitRead(node, c) } - predicate isFlowSink() { - this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or - this instanceof PathNodeSinkGroup - } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } - private string ppAp() { - this instanceof PathNodeSink and result = "" - or - exists(string s | s = this.(PathNodeMid).getAp().toString() | - if s = "" then result = "" else result = " " + s - ) - } + FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } - private string ppCtx() { - this instanceof PathNodeSink and result = "" - or - result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + predicate sourceGrouping(Node source, string sourceGroup) { + any(Configuration config).sourceGrouping(source, sourceGroup) } - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + predicate sinkGrouping(Node sink, string sinkGroup) { + any(Configuration config).sinkGrouping(sink, sinkGroup) } -} - -/** Holds if `n` can reach a sink. */ -private predicate directReach(PathNodeImpl n) { - n instanceof PathNodeSink or - n instanceof PathNodeSinkGroup or - directReach(n.getANonHiddenSuccessor()) -} - -/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ -private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } -/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { - n1.getANonHiddenSuccessor() = n2 and directReach(n2) + predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() } } -private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) +private import Impl as I +import I /** * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. */ -class PathNode instanceof PathNodeImpl { - PathNode() { reach(this) } - +class PathNode instanceof I::PathNode { /** Gets a textual representation of this element. */ final string toString() { result = super.toString() } @@ -3406,1548 +358,39 @@ class PathNode instanceof PathNodeImpl { } /** Gets the underlying `Node`. */ - final Node getNode() { super.getNodeEx().projectToNode() = result } + final Node getNode() { result = super.getNode() } /** Gets the `FlowState` of this node. */ - final FlowState getState() { result = super.getState() } + final FlowState getState() { result = getState(super.getState()) } /** Gets the associated configuration. */ - final Configuration getConfiguration() { result = super.getConfiguration() } + final Configuration getConfiguration() { result = getConfig(super.getState()) } /** Gets a successor of this node, if any. */ - final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } + final PathNode getASuccessor() { result = super.getASuccessor() } /** Holds if this node is a source. */ final predicate isSource() { super.isSource() } /** Holds if this node is a grouping of source nodes. */ - final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group, _) } + final predicate isSourceGroup(string group) { super.isSourceGroup(group) } /** Holds if this node is a grouping of sink nodes. */ - final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group, _) } + final predicate isSinkGroup(string group) { super.isSinkGroup(group) } } -/** - * Provides the query predicates needed to include a graph in a path-problem query. - */ -module PathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } - - /** Holds if `n` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode n, string key, string val) { - key = "semmle.label" and val = n.toString() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { - Subpaths::subpaths(arg, par, ret, out) - } -} - -/** - * An intermediate flow graph node. This is a triple consisting of a `Node`, - * a `CallContext`, and a `Configuration`. - */ -private class PathNodeMid extends PathNodeImpl, TPathNodeMid { - NodeEx node; - FlowState state; - CallContext cc; - SummaryCtx sc; - AccessPath ap; - Configuration config; - - PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - SummaryCtx getSummaryCtx() { result = sc } - - AccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - private PathNodeMid getSuccMid() { - pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx(), result.getAp()) and - result.getConfiguration() = unbindConf(this.getConfiguration()) - } - - override PathNodeImpl getASuccessorImpl() { - // an intermediate step to another intermediate node - result = this.getSuccMid() - or - // a final step to a sink - result = this.getSuccMid().projectToSink() - } - - override predicate isSource() { - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - } - - predicate isAtSink() { - sinkNode(node, state, config) and - ap instanceof AccessPathNil and - if hasSinkCallCtx(config) - then - // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` - // is exactly what we need to check. This also implies - // `sc instanceof SummaryCtxNone`. - // For `FeatureEqualSourceSinkCallContext` the initial call context was - // set to `CallContextSomeCall` and jumps are disallowed, so - // `cc instanceof CallContextNoCall` never holds. On the other hand, - // in this case there's never any need to enter a call except to identify - // a summary, so the condition in `pathIntoCallable` enforces this, which - // means that `sc instanceof SummaryCtxNone` holds if and only if we are - // in the call context of the source. - sc instanceof SummaryCtxNone or - cc instanceof CallContextNoCall - else any() - } - - PathNodeSink projectToSink() { - this.isAtSink() and - result.getNodeEx() = node and - result.getState() = state and - result.getConfiguration() = unbindConf(config) - } -} - -/** - * A flow graph node corresponding to a sink. This is disjoint from the - * intermediate nodes in order to uniquely correspond to a given sink by - * excluding the `CallContext`. - */ -private class PathNodeSink extends PathNodeImpl, TPathNodeSink { - NodeEx node; - FlowState state; - Configuration config; - - PathNodeSink() { this = TPathNodeSink(node, state, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result = TPathNodeSinkGroup(this.getSinkGroup(), config) - } - - override predicate isSource() { sourceNode(node, state, config) } - - string getSinkGroup() { config.sinkGrouping(node.asNode(), result) } -} - -private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { - string sourceGroup; - Configuration config; - - PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result.getSourceGroup() = sourceGroup and - result.getConfiguration() = config - } - - override predicate isSource() { none() } - - override string toString() { result = sourceGroup } - - override 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 - } -} - -private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { - string sinkGroup; - Configuration config; - - PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { none() } - - override predicate isSource() { none() } - - override string toString() { result = sinkGroup } - - override 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 - } -} - -private predicate pathNode( - PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, - Configuration conf, LocalCallContext localCC -) { - midnode = mid.getNodeEx() and - state = mid.getState() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - midnode.getEnclosingCallable()) and - ap = mid.getAp() -} - -/** - * Holds if data may flow from `mid` to `node`. The last step in or out of - * a callable is recorded by `cc`. - */ -pragma[assume_small_delta] -pragma[nomagic] -private predicate pathStep( - PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap -) { - exists(NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC | - pathNode(mid, midnode, state0, cc, sc, ap, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, true, _, conf, localCC) - ) - or - exists( - AccessPath ap0, NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC - | - pathNode(mid, midnode, state0, cc, sc, ap0, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), conf, - localCC) and - ap0 instanceof AccessPathNil - ) - or - jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - ap = mid.getAp() - or - additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, mid.getConfiguration()) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - pathIntoCallable(mid, node, state, _, cc, sc, _, _) and ap = mid.getAp() - or - pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone - or - pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() -} - -pragma[nomagic] -private predicate pathReadStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - tc = ap0.getHead() and - Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -pragma[nomagic] -private predicate pathStoreStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -private predicate pathOutOfCallable0( - PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa, - Configuration config -) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - apa = mid.getAp().getApprox() and - config = mid.getConfiguration() -} - -pragma[nomagic] -private predicate pathOutOfCallable1( - PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPathApprox apa, Configuration config -) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - pathOutOfCallable0(mid, pos, state, innercc, apa, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() +private predicate hasFlow(Node source, Node sink, Configuration config) { + exists(PathNode source0, PathNode sink0 | + hasFlowPath(source0, sink0, config) and + source0.getNode() = source and + sink0.getNode() = sink ) } -pragma[noinline] -private NodeEx getAnOutNodeFlow( - ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config -) { - result.asNode() = kind.getAnOutNode(call) and - Stage5::revFlow(result, _, apa, config) +private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) { + hasFlowPath(source, sink) and source.getConfiguration() = config } -/** - * Holds if data may flow from `mid` to `out`. The last step of this path - * is a return from a callable and is recorded by `cc`, if needed. - */ -pragma[noinline] -private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { - exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | - pathOutOfCallable1(mid, call, kind, state, cc, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} +private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) } -/** - * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. - */ -pragma[noinline] -private predicate pathIntoArg( - PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(ArgNodeEx arg, ArgumentPosition apos | - pathNode(mid, arg, state, cc, _, ap, config, _) and - arg.asNode().(ArgNode).argumentOf(call, apos) and - apa = ap.getApprox() and - parameterMatch(ppos, apos) - ) -} - -pragma[nomagic] -private predicate parameterCand( - DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::revFlow(p, _, apa, config) and - p.isParameterOf(callable, pos) - ) -} - -pragma[nomagic] -private predicate pathIntoCallable0( - PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, AccessPath ap, Configuration config -) { - exists(AccessPathApprox apa | - pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - callable = resolveCall(call, outercc) and - parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa), - pragma[only_bind_into](config)) - ) -} - -/** - * Holds if data may flow from `mid` to `p` through `call`. The contexts - * before and after entering the callable are `outercc` and `innercc`, - * respectively. - */ -pragma[nomagic] -private predicate pathIntoCallable( - PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, - SummaryCtx sc, DataFlowCall call, Configuration config -) { - exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | - pathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - ( - sc = TSummaryCtxSome(p, state, ap) - or - not exists(TSummaryCtxSome(p, state, ap)) and - sc = TSummaryCtxNone() and - // When the call contexts of source and sink needs to match then there's - // never any reason to enter a callable except to find a summary. See also - // the comment in `PathNodeMid::isAtSink`. - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) -} - -/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ -pragma[nomagic] -private predicate paramFlowsThrough( - ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, - AccessPathApprox apa, Configuration config -) { - exists(RetNodeEx ret | - pathNode(_, ret, state, cc, sc, ap, config, _) and - kind = ret.getKind() and - apa = ap.getApprox() and - parameterFlowThroughAllowed(sc.getParamNode(), kind) - ) -} - -pragma[nomagic] -private predicate pathThroughCallable0( - DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(CallContext innercc, SummaryCtx sc | - pathIntoCallable(mid, _, _, cc, innercc, sc, call, config) and - paramFlowsThrough(kind, state, innercc, sc, ap, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` through a callable to the node `out`. - * The context `cc` is restored to its value prior to entering the callable. - */ -pragma[noinline] -private predicate pathThroughCallable( - PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap -) { - exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config | - pathThroughCallable0(call, mid, kind, state, cc, ap, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -private module Subpaths { - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths01( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - exists(Configuration config | - pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and - pathIntoCallable(arg, par, _, _, innercc, sc, _, config) and - paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, - pragma[only_bind_into](apout), _, unbindConf(config)) and - not arg.isHidden() - ) - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `sout`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths02( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and - out.asNode() = kind.getAnOutNode(_) - } - - pragma[nomagic] - private Configuration getPathNodeConf(PathNodeImpl n) { result = n.getConfiguration() } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple. - */ - pragma[nomagic] - private predicate subpaths03( - PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, AccessPath apout - ) { - exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | - subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and - pathNode(ret, retnode, sout, innercc, sc, apout, unbindConf(getPathNodeConf(arg)), _) and - kind = retnode.getKind() - ) - } - - private PathNodeImpl localStepToHidden(PathNodeImpl n) { - n.getASuccessorImpl() = result and - result.isHidden() and - exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | - localFlowBigStep(n1, _, n2, _, _, _, _, _) or - store(n1, _, n2, _, _) or - readSet(n1, _, n2, _) - ) - } - - pragma[nomagic] - private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { - succ = pred.getANonHiddenSuccessor() and - succNode = succ.getNodeEx() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { - exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | - pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and - subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and - hasSuccessor(pragma[only_bind_into](arg), par, p) and - not ret.isHidden() and - pathNode(out0, o, sout, _, _, apout, _, _) - | - out = out0 or out = out0.projectToSink() - ) - } - - /** - * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. - */ - predicate retReach(PathNodeImpl n) { - exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) - or - exists(PathNodeImpl mid | - retReach(mid) and - n.getANonHiddenSuccessor() = mid and - not subpaths(_, mid, _, _) - ) - } -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -private predicate hasFlowPath( - PathNodeImpl flowsource, PathNodeImpl flowsink, Configuration configuration -) { - flowsource.isFlowSource() and - flowsource.getConfiguration() = configuration and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.isFlowSink() -} - -private predicate flowsTo( - PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink, - Configuration configuration -) { - flowsource.isSource() and - flowsource.getConfiguration() = configuration and - flowsource.getNodeEx().asNode() = source and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.getNodeEx().asNode() = sink -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -predicate flowsTo(Node source, Node sink, Configuration configuration) { - flowsTo(_, _, source, sink, configuration) -} - -private predicate finalStats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples -) { - fwd = true and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and - tuples = count(PathNodeImpl pn) - or - fwd = false and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and - tuples = count(PathNode pn) -} - -/** - * INTERNAL: Only for debugging. - * - * Calculates per-stage metrics for data flow. - */ -predicate stageStats( - int n, string stage, int nodes, int fields, int conscand, int states, int tuples, - Configuration config -) { - stage = "1 Fwd" and - n = 10 and - Stage1::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "1 Rev" and - n = 15 and - Stage1::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Fwd" and - n = 20 and - Stage2::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Rev" and - n = 25 and - Stage2::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Fwd" and - n = 30 and - Stage3::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Rev" and - n = 35 and - Stage3::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Fwd" and - n = 40 and - Stage4::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Rev" and - n = 45 and - Stage4::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Fwd" and - n = 50 and - Stage5::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Rev" and - n = 55 and - Stage5::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) - or - stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) -} - -private module FlowExploration { - private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - jumpStep(node1, node2, config) - or - additionalJumpStep(node1, node2, config) - or - additionalJumpStateStep(node1, _, node2, _, config) - or - // flow into callable - viableParamArgEx(_, node2, node1) - or - // flow out of a callable - viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) - | - c1 = node1.getEnclosingCallable() and - c2 = node2.getEnclosingCallable() and - c1 != c2 - ) - } - - private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSource(n) or config.isSource(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSrc(mid, config) and callableStep(mid, c, config) - ) - } - - private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSink(n) or config.isSink(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSink(mid, config) and callableStep(c, mid, config) - ) - } - - private newtype TCallableExt = - TCallable(DataFlowCallable c, Configuration config) { - interestingCallableSrc(c, config) or - interestingCallableSink(c, config) - } or - TCallableSrc() or - TCallableSink() - - private predicate callableExtSrc(TCallableSrc src) { any() } - - private predicate callableExtSink(TCallableSink sink) { any() } - - private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { - exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | - callableStep(c1, c2, config) and - ce1 = TCallable(c1, pragma[only_bind_into](config)) and - ce2 = TCallable(c2, pragma[only_bind_into](config)) - ) - or - exists(Node n, Configuration config | - ce1 = TCallableSrc() and - (config.isSource(n) or config.isSource(n, _)) and - ce2 = TCallable(getNodeEnclosingCallable(n), config) - ) - or - exists(Node n, Configuration config | - ce2 = TCallableSink() and - (config.isSink(n) or config.isSink(n, _)) and - ce1 = TCallable(getNodeEnclosingCallable(n), config) - ) - } - - private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { - callableExtStepFwd(ce2, ce1) - } - - private int distSrcExt(TCallableExt c) = - shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) - - private int distSinkExt(TCallableExt c) = - shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) - - private int distSrc(DataFlowCallable c, Configuration config) { - result = distSrcExt(TCallable(c, config)) - 1 - } - - private int distSink(DataFlowCallable c, Configuration config) { - result = distSinkExt(TCallable(c, config)) - 1 - } - - private newtype TPartialAccessPath = - TPartialNil(DataFlowType t) or - TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first - * element of the list and its length are tracked. If data flows from a source to - * a given node with a given `AccessPath`, this indicates the sequence of - * dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ - private class PartialAccessPath extends TPartialAccessPath { - abstract string toString(); - - TypedContent getHead() { this = TPartialCons(result, _) } - - int len() { - this = TPartialNil(_) and result = 0 - or - this = TPartialCons(_, result) - } - - DataFlowType getType() { - this = TPartialNil(result) - or - exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) - } - } - - private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { - override string toString() { - exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) - } - } - - private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { - override string toString() { - exists(TypedContent tc, int len | this = TPartialCons(tc, len) | - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private newtype TRevPartialAccessPath = - TRevPartialNil() or - TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `Content`s, but only the first - * element of the list and its length are tracked. - */ - private class RevPartialAccessPath extends TRevPartialAccessPath { - abstract string toString(); - - Content getHead() { this = TRevPartialCons(result, _) } - - int len() { - this = TRevPartialNil() and result = 0 - or - this = TRevPartialCons(_, result) - } - } - - private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { - override string toString() { result = "" } - } - - private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { - override string toString() { - exists(Content c, int len | this = TRevPartialCons(c, len) | - if len = 1 - then result = "[" + c.toString() + "]" - else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private predicate relevantState(FlowState state) { - sourceNode(_, state, _) or - sinkNode(_, state, _) or - additionalLocalStateStep(_, state, _, _, _) or - additionalLocalStateStep(_, _, _, state, _) or - additionalJumpStateStep(_, state, _, _, _) or - additionalJumpStateStep(_, _, _, state, _) - } - - private newtype TSummaryCtx1 = - TSummaryCtx1None() or - TSummaryCtx1Param(ParamNodeEx p) - - private newtype TSummaryCtx2 = - TSummaryCtx2None() or - TSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TSummaryCtx3 = - TSummaryCtx3None() or - TSummaryCtx3Some(PartialAccessPath ap) - - private newtype TRevSummaryCtx1 = - TRevSummaryCtx1None() or - TRevSummaryCtx1Some(ReturnPosition pos) - - private newtype TRevSummaryCtx2 = - TRevSummaryCtx2None() or - TRevSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TRevSummaryCtx3 = - TRevSummaryCtx3None() or - TRevSummaryCtx3Some(RevPartialAccessPath ap) - - private newtype TPartialPathNode = - TPartialPathNodeFwd( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = TPartialNil(node.getDataFlowType()) and - exists(config.explorationLimit()) - or - partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap, config) and - distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() - } or - TPartialPathNodeRev( - NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, - RevPartialAccessPath ap, Configuration config - ) { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() and - exists(config.explorationLimit()) - or - revPartialPathStep(_, node, state, sc1, sc2, sc3, ap, config) and - not clearsContentEx(node, ap.getHead()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead()) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() - } - - pragma[nomagic] - private predicate partialPathNodeMk0( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap, config) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - not clearsContentEx(node, ap.getHead().getContent()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead().getContent()) - ) and - if node.asNode() instanceof CastingNode - then compatibleTypes(node.getDataFlowType(), ap.getType()) - else any() - } - - /** - * A `Node` augmented with a call context, an access path, and a configuration. - */ - class PartialPathNode extends TPartialPathNode { - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() - } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - /** Gets the underlying `Node`. */ - final Node getNode() { this.getNodeEx().projectToNode() = result } - - FlowState getState() { none() } - - private NodeEx getNodeEx() { - result = this.(PartialPathNodeFwd).getNodeEx() or - result = this.(PartialPathNodeRev).getNodeEx() - } - - /** Gets the associated configuration. */ - Configuration getConfiguration() { none() } - - /** Gets a successor of this node, if any. */ - PartialPathNode getASuccessor() { none() } - - /** - * Gets the approximate distance to the nearest source measured in number - * of interprocedural steps. - */ - int getSourceDistance() { - result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - /** - * Gets the approximate distance to the nearest sink measured in number - * of interprocedural steps. - */ - int getSinkDistance() { - result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - private string ppAp() { - exists(string s | - s = this.(PartialPathNodeFwd).getAp().toString() or - s = this.(PartialPathNodeRev).getAp().toString() - | - if s = "" then result = "" else result = " " + s - ) - } - - private string ppCtx() { - result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" - } - - /** Holds if this is a source in a forward-flow path. */ - predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } - - /** Holds if this is a sink in a reverse-flow path. */ - predicate isRevSink() { this.(PartialPathNodeRev).isSink() } - } - - /** - * Provides the query predicates needed to include a graph in a path-problem query. - */ - module PartialPathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } - } - - private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { - NodeEx node; - FlowState state; - CallContext cc; - TSummaryCtx1 sc1; - TSummaryCtx2 sc2; - TSummaryCtx3 sc3; - PartialAccessPath ap; - Configuration config; - - PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - TSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TSummaryCtx3 getSummaryCtx3() { result = sc3 } - - PartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeFwd getASuccessor() { - partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp(), - result.getConfiguration()) - } - - predicate isSource() { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap instanceof TPartialNil - } - } - - private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { - NodeEx node; - FlowState state; - TRevSummaryCtx1 sc1; - TRevSummaryCtx2 sc2; - TRevSummaryCtx3 sc3; - RevPartialAccessPath ap; - Configuration config; - - PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } - - RevPartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeRev getASuccessor() { - revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), - this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp(), this.getConfiguration()) - } - - predicate isSink() { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() - } - } - - private predicate partialPathStep( - PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, - TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and - ( - localFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - ) - or - jumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - partialPathStoreStep(mid, _, _, node, ap) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(PartialAccessPath ap0, TypedContent tc | - partialPathReadStep(mid, ap0, tc, node, cc, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsFwd(ap, tc, ap0, config) - ) - or - partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap, config) - or - partialPathOutOfCallable(mid, node, state, cc, ap, config) and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() - or - partialPathThroughCallable(mid, node, state, cc, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - bindingset[result, i] - private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } - - pragma[inline] - private predicate partialPathStoreStep( - PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, - PartialAccessPath ap2 - ) { - exists(NodeEx midNode, DataFlowType contentType | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - store(midNode, tc, node, contentType, mid.getConfiguration()) and - ap2.getHead() = tc and - ap2.len() = unbindInt(ap1.len() + 1) and - compatibleTypes(ap1.getType(), contentType) - ) - } - - pragma[nomagic] - private predicate apConsFwd( - PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeFwd mid | - partialPathStoreStep(mid, ap1, tc, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate partialPathReadStep( - PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc, - Configuration config - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and - ap.getHead() = tc and - pragma[only_bind_into](config) = mid.getConfiguration() and - cc = mid.getCallContext() - ) - } - - private predicate partialPathOutOfCallable0( - PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, - PartialAccessPath ap, Configuration config - ) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - ap = mid.getAp() and - config = mid.getConfiguration() - } - - pragma[nomagic] - private predicate partialPathOutOfCallable1( - PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - partialPathOutOfCallable0(mid, pos, state, innercc, ap, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) - } - - private predicate partialPathOutOfCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(ReturnKindExt kind, DataFlowCall call | - partialPathOutOfCallable1(mid, call, kind, state, cc, ap, config) - | - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[noinline] - private predicate partialPathIntoArg( - PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ArgNode arg, ArgumentPosition apos | - arg = mid.getNodeEx().asNode() and - state = mid.getState() and - cc = mid.getCallContext() and - arg.argumentOf(call, apos) and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate partialPathIntoCallable0( - PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - partialPathIntoArg(mid, pos, state, outercc, call, ap, config) and - callable = resolveCall(call, outercc) - } - - private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, - CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ParameterPosition pos, DataFlowCallable callable | - partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - sc1 = TSummaryCtx1Param(p) and - sc2 = TSummaryCtx2Some(state) and - sc3 = TSummaryCtx3Some(ap) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) - } - - pragma[nomagic] - private predicate paramFlowsThroughInPartialPath( - ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeFwd mid, RetNodeEx ret | - mid.getNodeEx() = ret and - kind = ret.getKind() and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() and - ap = mid.getAp() - ) - } - - pragma[noinline] - private predicate partialPathThroughCallable0( - DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | - partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _, config) and - paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap, config) - ) - } - - private predicate partialPathThroughCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ReturnKindExt kind | - partialPathThroughCallable0(call, mid, kind, state, cc, ap, config) and - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[nomagic] - private predicate revPartialPathStep( - PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, - TRevSummaryCtx3 sc3, RevPartialAccessPath ap, Configuration config - ) { - localFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - jumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - revPartialPathReadStep(mid, _, _, node, ap) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(RevPartialAccessPath ap0, Content c | - revPartialPathStoreStep(mid, ap0, c, node, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsRev(ap, c, ap0, config) - ) - or - exists(ParamNodeEx p | - mid.getNodeEx() = p and - viableParamArgEx(_, p, node) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - or - exists(ReturnPosition pos | - revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap, config) and - pos = getReturnPosition(node.asNode()) - ) - or - revPartialPathThroughCallable(mid, node, state, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - pragma[inline] - private predicate revPartialPathReadStep( - PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, - RevPartialAccessPath ap2 - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - read(node, c, midNode, mid.getConfiguration()) and - ap2.getHead() = c and - ap2.len() = unbindInt(ap1.len() + 1) - ) - } - - pragma[nomagic] - private predicate apConsRev( - RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeRev mid | - revPartialPathReadStep(mid, ap1, c, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathStoreStep( - PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config - ) { - exists(NodeEx midNode, TypedContent tc | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - store(node, tc, midNode, _, config) and - ap.getHead() = c and - config = mid.getConfiguration() and - tc.getContent() = c - ) - } - - pragma[nomagic] - private predicate revPartialPathIntoReturn( - PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, - TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap, - Configuration config - ) { - exists(NodeEx out | - mid.getNodeEx() = out and - mid.getState() = state and - viableReturnPosOutEx(call, pos, out) and - sc1 = TRevSummaryCtx1Some(pos) and - sc2 = TRevSummaryCtx2Some(state) and - sc3 = TRevSummaryCtx3Some(ap) and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathFlowsThrough( - ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, - TRevSummaryCtx3Some sc3, RevPartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | - mid.getNodeEx() = p and - mid.getState() = state and - p.getPosition() = ppos and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable0( - DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, - RevPartialAccessPath ap, Configuration config - ) { - exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | - revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _, config) and - revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap, config) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ArgumentPosition pos | - revPartialPathThroughCallable0(call, mid, pos, state, ap, config) and - node.asNode().(ArgNode).argumentOf(call, pos) - ) - } -} - -import FlowExploration - -private predicate partialFlow( - PartialPathNode source, PartialPathNode node, Configuration configuration -) { - source.getConfiguration() = configuration and - source.isFwdSource() and - node = source.getASuccessor+() -} - -private predicate revPartialFlow( - PartialPathNode node, PartialPathNode sink, Configuration configuration -) { - sink.getConfiguration() = configuration and - sink.isRevSink() and - node.getASuccessor+() = sink -} +predicate flowsTo = hasFlow/3; diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll index 1b969756b09..e6bdc74cceb 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll @@ -1,16 +1,16 @@ /** - * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from - * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed - * through the `Configuration` class. This file exists in several identical - * copies, allowing queries to use multiple `Configuration` classes that depend - * on each other without introducing mutual recursion among those configurations. + * DEPRECATED: Use `Make` and `MakeWithState` instead. + * + * Provides a `Configuration` class backwards-compatible interface to the data + * flow library. */ private import DataFlowImplCommon private import DataFlowImplSpecific::Private import DataFlowImplSpecific::Public +private import DataFlowImpl import DataFlowImplCommonPublic +import FlowStateString /** * A configuration of interprocedural data flow analysis. This defines @@ -144,6 +144,10 @@ abstract class Configuration extends string { * - `FeatureEqualSourceSinkCallContext`: * Implies both of the above and additionally ensures that the entire flow * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. */ FlowFeature getAFeature() { none() } @@ -156,7 +160,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from `source` to `sink` for this configuration. */ - predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } + predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) } /** * Holds if data may flow from `source` to `sink` for this configuration. @@ -169,9 +173,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from some source to `sink` for this configuration. */ - predicate hasFlowTo(Node sink) { - sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode() - } + predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) } /** * Holds if data may flow from some source to `sink` for this configuration. @@ -179,10 +181,12 @@ abstract class Configuration extends string { predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } /** + * DEPRECATED: Use `FlowExploration` instead. + * * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` * measured in approximate number of interprocedural steps. */ - int explorationLimit() { none() } + deprecated int explorationLimit() { none() } /** * Holds if hidden nodes should be included in the data flow graph. @@ -191,49 +195,6 @@ abstract class Configuration extends string { * is not visualized (for example in a `path-problem` query). */ predicate includeHiddenNodes() { none() } - - /** - * Holds if there is a partial data flow path from `source` to `node`. The - * approximate distance between `node` and the closest source is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards sink definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sources is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - */ - final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { - partialFlow(source, node, this) and - dist = node.getSourceDistance() - } - - /** - * Holds if there is a partial data flow path from `node` to `sink`. The - * approximate distance between `node` and the closest sink is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards source definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sinks is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - * - * Note that reverse flow has slightly lower precision than the corresponding - * forward flow, as reverse flow disregards type pruning among other features. - */ - final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { - revPartialFlow(node, sink, this) and - dist = node.getSinkDistance() - } } /** @@ -263,90 +224,6 @@ abstract private class ConfigurationRecursionPrevention extends Configuration { } } -private newtype TNodeEx = - TNodeNormal(Node n) or - TNodeImplicitRead(Node n, boolean hasRead) { - any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true] - } - -private class NodeEx extends TNodeEx { - string toString() { - result = this.asNode().toString() - or - exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") - } - - Node asNode() { this = TNodeNormal(result) } - - predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } - - Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } - - pragma[nomagic] - private DataFlowCallable getEnclosingCallable0() { - nodeEnclosingCallable(this.projectToNode(), result) - } - - pragma[inline] - DataFlowCallable getEnclosingCallable() { - pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) - } - - pragma[nomagic] - private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } - - pragma[inline] - DataFlowType getDataFlowType() { - pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) - } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -private class ArgNodeEx extends NodeEx { - ArgNodeEx() { this.asNode() instanceof ArgNode } -} - -private class ParamNodeEx extends NodeEx { - ParamNodeEx() { this.asNode() instanceof ParamNode } - - predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { - this.asNode().(ParamNode).isParameterOf(c, pos) - } - - ParameterPosition getPosition() { this.isParameterOf(_, result) } -} - -private class RetNodeEx extends NodeEx { - RetNodeEx() { this.asNode() instanceof ReturnNodeExt } - - ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } - - ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } -} - -private predicate inBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierIn(n) - | - config.isSource(n) or config.isSource(n, _) - ) -} - -private predicate outBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierOut(n) - | - config.isSink(n) or config.isSink(n, _) - ) -} - /** A bridge class to access the deprecated `isBarrierGuard`. */ private class BarrierGuardGuardedNodeBridge extends Unit { abstract predicate guardedNode(Node n, Configuration config); @@ -370,3019 +247,94 @@ private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { } } -pragma[nomagic] -private predicate fullBarrier(NodeEx node, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n) +private FlowState relevantState(Configuration config) { + config.isSource(_, result) or + config.isSink(_, result) or + config.isBarrier(_, result) or + config.isAdditionalFlowStep(_, result, _, _) or + config.isAdditionalFlowStep(_, _, _, result) +} + +private newtype TConfigState = + TMkConfigState(Configuration config, FlowState state) { + state = relevantState(config) or state instanceof FlowStateEmpty + } + +private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) } + +private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) } + +private predicate singleConfiguration() { 1 = strictcount(Configuration c) } + +private module Config implements FullStateConfigSig { + class FlowState = TConfigState; + + predicate isSource(Node source, FlowState state) { + getConfig(state).isSource(source, getState(state)) or - config.isBarrierIn(n) and - not config.isSource(n) and - not config.isSource(n, _) + getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty + } + + predicate isSink(Node sink, FlowState state) { + getConfig(state).isSink(sink, getState(state)) or - config.isBarrierOut(n) and - not config.isSink(n) and - not config.isSink(n, _) + getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty + } + + predicate isBarrier(Node node) { none() } + + predicate isBarrier(Node node, FlowState state) { + getConfig(state).isBarrier(node, getState(state)) or + getConfig(state).isBarrier(node) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state)) + } + + predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) } + + predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) } + + predicate isAdditionalFlowStep(Node node1, Node node2) { + singleConfiguration() and + any(Configuration config).isAdditionalFlowStep(node1, node2) + } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and + getConfig(state2) = getConfig(state1) or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config) - ) -} - -pragma[nomagic] -private predicate stateBarrier(NodeEx node, FlowState state, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n, state) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config) - ) -} - -pragma[nomagic] -private predicate sourceNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSource(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSource(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -pragma[nomagic] -private predicate sinkNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSink(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSink(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -/** Provides the relevant barriers for a step from `node1` to `node2`. */ -pragma[inline] -private predicate stepFilter(NodeEx node1, NodeEx node2, Configuration config) { - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) -} - -/** - * Holds if data can flow in one local step from `node1` to `node2`. - */ -private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.asNode() = n and - node2.isImplicitReadNode(n, false) and - not fullBarrier(node1, config) - ) -} - -/** - * Holds if the additional step from `node1` to `node2` does not jump between callables. - */ -private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.isImplicitReadNode(n, true) and - node2.asNode() = n and - not fullBarrier(node2, config) - ) -} - -private predicate additionalLocalStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) - ) -} - -/** - * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. - */ -private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if the additional step from `node1` to `node2` jumps between callables. - */ -private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate additionalJumpStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -pragma[nomagic] -private predicate readSet(NodeEx node1, ContentSet c, NodeEx node2, Configuration config) { - readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and - stepFilter(node1, node2, config) - or - exists(Node n | - node2.isImplicitReadNode(n, true) and - node1.isImplicitReadNode(n, _) and - config.allowImplicitRead(n, c) - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(ContentSet cs | - readSet(node1, cs, node2, config) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate clearsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - clearsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate expectsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - expectsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -pragma[nomagic] -private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } - -pragma[nomagic] -private predicate hasReadStep(Content c, Configuration config) { read(_, c, _, config) } - -pragma[nomagic] -private predicate store( - NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config -) { - store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), - contentType) and - hasReadStep(tc.getContent(), config) and - stepFilter(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { - viableReturnPosOut(call, pos, out.asNode()) -} - -pragma[nomagic] -private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { - viableParamArg(call, p.asNode(), arg.asNode()) -} - -/** - * Holds if field flow should be used for the given configuration. - */ -private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } - -private predicate hasSourceCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSourceCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate hasSinkCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSinkCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if flow from `p` to a return node of kind `kind` is allowed. - * - * We don't expect a parameter to return stored in itself, unless - * explicitly allowed - */ -bindingset[p, kind] -private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { - exists(ParameterPosition pos | p.isParameterOf(_, pos) | - not kind.(ParamUpdateReturnKind).getPosition() = pos - or - allowParameterReturnInSelfCached(p.asNode()) - ) -} - -private module Stage1 implements StageSig { - class Ap extends int { - // workaround for bad functionality-induced joins (happens when using `Unit`) - pragma[nomagic] - Ap() { this in [0 .. 1] and this < 1 } - } - - private class Cc = boolean; - - /* Begin: Stage 1 logic. */ - /** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `cc` records whether the node is reached through an - * argument in a call. - */ - private predicate fwdFlow(NodeEx node, Cc cc, Configuration config) { - sourceNode(node, _, config) and - if hasSourceCallCtx(config) then cc = true else cc = false - or - exists(NodeEx mid | fwdFlow(mid, cc, config) | - localFlowStep(mid, node, config) or - additionalLocalFlowStep(mid, node, config) or - additionalLocalStateStep(mid, _, node, _, config) - ) - or - exists(NodeEx mid | fwdFlow(mid, _, config) and cc = false | - jumpStep(mid, node, config) or - additionalJumpStep(mid, node, config) or - additionalJumpStateStep(mid, _, node, _, config) - ) - or - // store - exists(NodeEx mid | - useFieldFlow(config) and - fwdFlow(mid, cc, config) and - store(mid, _, node, _, config) - ) - or - // read - exists(ContentSet c | - fwdFlowReadSet(c, node, cc, config) and - fwdFlowConsCandSet(c, _, config) - ) - or - // flow into a callable - fwdFlowIn(_, _, _, node, config) and - cc = true - or - // flow out of a callable - fwdFlowOut(_, node, false, config) and - cc = false - or - // flow through a callable - exists(DataFlowCall call | - fwdFlowOutFromArg(call, node, config) and - fwdFlowIsEntered(call, cc, config) - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowIn( - DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p, Configuration config - ) { - // call context cannot help reduce virtual dispatch - fwdFlow(arg, cc, config) and - viableParamArgEx(call, p, arg) and - not fullBarrier(p, config) and - ( - cc = false - or - cc = true and - not reducedViableImplInCallContext(call, _, _) - ) - or - // call context may help reduce virtual dispatch - exists(DataFlowCallable target | - fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target, config) and - target = viableImplInSomeFwdFlowCallContextExt(call, config) and - cc = true - ) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - fwdFlowIn(call, _, cc, _, config) - } - - pragma[nomagic] - private predicate fwdFlowInReducedViableImplInSomeCallContext( - DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target, Configuration config - ) { - fwdFlow(arg, true, config) and - viableParamArgEx(call, p, arg) and - reducedViableImplInCallContext(call, _, _) and - target = p.getEnclosingCallable() and - not fullBarrier(p, config) - } - - /** - * Gets a viable dispatch target of `call` in the context `ctx`. This is - * restricted to those `call`s for which a context might make a difference, - * and to `ctx`s that are reachable in `fwdFlow`. - */ - pragma[nomagic] - private DataFlowCallable viableImplInSomeFwdFlowCallContextExt( - DataFlowCall call, Configuration config - ) { - exists(DataFlowCall ctx | - fwdFlowIsEntered(ctx, _, config) and - result = viableImplInCallContextExt(call, ctx) - ) - } - - private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) } - - pragma[nomagic] - private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc, Configuration config) { - exists(NodeEx mid | - fwdFlow(mid, cc, config) and - readSet(mid, c, node, config) - ) - } - - /** - * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - fwdFlow(mid, _, config) and - store(mid, tc, node, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `cs` may be interpreted in a read as the target of some store - * into `c`, in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCandSet(ContentSet cs, Content c, Configuration config) { - fwdFlowConsCand(c, config) and - c = cs.getAReadContent() - } - - pragma[nomagic] - private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { - exists(RetNodeEx ret | - fwdFlow(ret, cc, config) and - ret.getReturnPosition() = pos - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) { - exists(ReturnPosition pos | - fwdFlowReturnPosition(pos, cc, config) and - viableReturnPosOutEx(call, pos, out) and - not fullBarrier(out, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) { - fwdFlowOut(call, out, true, config) - } - - private predicate stateStepFwd(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1 | - additionalLocalStateStep(node1, state1, _, state2, config) or - additionalJumpStateStep(node1, state1, _, state2, config) - | - fwdFlow(node1, config) - ) - } - - private predicate fwdFlowState(FlowState state, Configuration config) { - sourceNode(_, state, config) - or - exists(FlowState state0 | - fwdFlowState(state0, config) and - stateStepFwd(state0, state, config) - ) - } - - /** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink. - */ - pragma[nomagic] - private predicate revFlow(NodeEx node, boolean toReturn, Configuration config) { - revFlow0(node, toReturn, config) and - fwdFlow(node, config) - } - - pragma[nomagic] - private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) { - exists(FlowState state | - fwdFlow(node, pragma[only_bind_into](config)) and - sinkNode(node, state, config) and - fwdFlowState(state, pragma[only_bind_into](config)) and - if hasSinkCallCtx(config) then toReturn = true else toReturn = false - ) - or - exists(NodeEx mid | revFlow(mid, toReturn, config) | - localFlowStep(node, mid, config) or - additionalLocalFlowStep(node, mid, config) or - additionalLocalStateStep(node, _, mid, _, config) - ) - or - exists(NodeEx mid | revFlow(mid, _, config) and toReturn = false | - jumpStep(node, mid, config) or - additionalJumpStep(node, mid, config) or - additionalJumpStateStep(node, _, mid, _, config) - ) - or - // store - exists(Content c | - revFlowStore(c, node, toReturn, config) and - revFlowConsCand(c, config) - ) - or - // read - exists(NodeEx mid, ContentSet c | - readSet(node, c, mid, config) and - fwdFlowConsCandSet(c, _, pragma[only_bind_into](config)) and - revFlow(mid, toReturn, pragma[only_bind_into](config)) - ) - or - // flow into a callable - revFlowIn(_, node, false, config) and - toReturn = false - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(pos, config) and - node.(RetNodeEx).getReturnPosition() = pos and - toReturn = true - ) - or - // flow through a callable - exists(DataFlowCall call | - revFlowInToReturn(call, node, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - /** - * Holds if `c` is the target of a read in the flow covered by `revFlow`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, ContentSet cs | - fwdFlow(node, pragma[only_bind_into](config)) and - readSet(node, cs, mid, config) and - fwdFlowConsCandSet(cs, c, pragma[only_bind_into](config)) and - revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) { - exists(NodeEx mid, TypedContent tc | - revFlow(mid, toReturn, pragma[only_bind_into](config)) and - fwdFlowConsCand(c, pragma[only_bind_into](config)) and - store(node, tc, mid, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `c` is the target of both a read and a store in the flow covered - * by `revFlow`. - */ - pragma[nomagic] - additional predicate revFlowIsReadAndStored(Content c, Configuration conf) { - revFlowConsCand(c, conf) and - revFlowStore(c, _, _, conf) - } - - pragma[nomagic] - additional predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config - ) { - fwdFlowReturnPosition(pos, _, config) and - viableReturnPosOutEx(call, pos, out) - } - - pragma[nomagic] - private predicate revFlowOut(ReturnPosition pos, Configuration config) { - exists(NodeEx out | - revFlow(out, _, config) and - viableReturnPosOutNodeCandFwd1(_, pos, out, config) - ) - } - - pragma[nomagic] - additional predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config - ) { - fwdFlowIn(call, arg, _, p, config) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate revFlowIn( - DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config - ) { - exists(ParamNodeEx p | - revFlow(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) { - revFlowIn(call, arg, true, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(NodeEx out | - revFlow(out, toReturn, config) and - fwdFlowOutFromArg(call, out, config) - ) - } - - private predicate stateStepRev(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - additionalLocalStateStep(node1, state1, node2, state2, config) or - additionalJumpStateStep(node1, state1, node2, state2, config) - | - revFlow(node1, _, pragma[only_bind_into](config)) and - revFlow(node2, _, pragma[only_bind_into](config)) and - fwdFlowState(state1, pragma[only_bind_into](config)) and - fwdFlowState(state2, pragma[only_bind_into](config)) - ) - } - - additional predicate revFlowState(FlowState state, Configuration config) { - exists(NodeEx node | - sinkNode(node, state, config) and - revFlow(node, _, pragma[only_bind_into](config)) and - fwdFlowState(state, pragma[only_bind_into](config)) - ) - or - exists(FlowState state0 | - revFlowState(state0, config) and - stateStepRev(state, state0, config) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Content c | - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - revFlow(node2, pragma[only_bind_into](config)) and - store(node1, tc, node2, contentType, config) and - c = tc.getContent() and - exists(ap1) - ) - } - - pragma[nomagic] - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) { - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - read(n1, c, n2, pragma[only_bind_into](config)) and - revFlow(n2, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, config) and - exists(ap) - } - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, _, pragma[only_bind_into](config)) and - exists(state) and - exists(ap) - } - - private predicate throughFlowNodeCand(NodeEx node, Configuration config) { - revFlow(node, true, config) and - fwdFlow(node, true, config) and - not inBarrier(node, config) and - not outBarrier(node, config) - } - - /** Holds if flow may return from `callable`. */ - pragma[nomagic] - private predicate returnFlowCallableNodeCand( - DataFlowCallable callable, ReturnKindExt kind, Configuration config - ) { - exists(RetNodeEx ret | - throughFlowNodeCand(ret, config) and - callable = ret.getEnclosingCallable() and - kind = ret.getKind() - ) - } - - /** - * Holds if flow may enter through `p` and reach a return node making `p` a - * candidate for the origin of a summary. - */ - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(DataFlowCallable c, ReturnKindExt kind | - throughFlowNodeCand(p, config) and - returnFlowCallableNodeCand(c, kind, config) and - p.getEnclosingCallable() = c and - exists(ap) and - parameterFlowThroughAllowed(p, kind) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - throughFlowNodeCand(ret, config) and - kind = ret.getKind() and - exists(argAp) and - exists(ap) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, boolean toReturn | - revFlow(arg, toReturn, config) and - revFlowInToReturn(call, arg, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, config)) and - fields = count(Content f0 | fwdFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | fwdFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, config)) and - fields = count(Content f0 | revFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | revFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | revFlow(n, b, config)) - } - /* End: Stage 1 logic. */ -} - -pragma[noinline] -private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - localFlowStep(node1, node2, config) -} - -pragma[noinline] -private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - additionalLocalFlowStep(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCand1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config -) { - Stage1::revFlow(out, config) and - Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, Configuration config -) { - exists(ReturnPosition pos | - viableReturnPosOutNodeCand1(call, pos, out, config) and - pos = ret.getReturnPosition() and - kind = pos.getKind() and - Stage1::revFlow(ret, config) and - not outBarrier(ret, config) and - not inBarrier(out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCand1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config -) { - Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and - Stage1::revFlow(arg, config) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config -) { - viableParamArgNodeCand1(call, p, arg, config) and - Stage1::revFlow(p, config) and - not outBarrier(arg, config) and - not inBarrier(p, config) -} - -/** - * Gets the amount of forward branching on the origin of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int branch(NodeEx n1, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n1, _, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) - ) -} - -/** - * Gets the amount of backward branching on the target of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int join(NodeEx n2, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n, _, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) - ) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. The - * `allowsFieldFlow` flag indicates whether the branching is within the limit - * specified by the configuration. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, ret, kind, out, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(ret, pragma[only_bind_into](config)) and - j = join(out, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. The `allowsFieldFlow` flag indicates whether - * the branching is within the limit specified by the configuration. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config -) { - flowIntoCallNodeCand1(call, arg, p, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(arg, pragma[only_bind_into](config)) and - j = join(p, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -private signature module StageSig { - class Ap; - - predicate revFlow(NodeEx node, Configuration config); - - predicate revFlowAp(NodeEx node, Ap ap, Configuration config); - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config); - - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config); - - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config); - - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ); - - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ); - - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config); -} - -private module MkStage { - class ApApprox = PrevStage::Ap; - - signature module StageParam { - class Ap; - - class ApNil extends Ap; - - bindingset[result, ap] - ApApprox getApprox(Ap ap); - - ApNil getApNil(NodeEx node); - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail); - - /** - * An approximation of `Content` that corresponds to the precision level of - * `Ap`, such that the mappings from both `Ap` and `Content` to this type - * are functional. - */ - class ApHeadContent; - - ApHeadContent getHeadContent(Ap ap); - - ApHeadContent projectToHeadContent(Content c); - - class ApOption; - - ApOption apNone(); - - ApOption apSome(Ap ap); - - class Cc; - - class CcCall extends Cc; - - // TODO: member predicate on CcCall - predicate matchesCall(CcCall cc, DataFlowCall call); - - class CcNoCall extends Cc; - - Cc ccNone(); - - CcCall ccSomeCall(); - - class LocalCc; - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc); - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ); - - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config - ); - - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config - ); - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config); - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType); - } - - module Stage implements StageSig { - import Param - - /* Begin: Stage logic. */ - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - private predicate revFlowApAlias(NodeEx node, ApApprox apa, Configuration config) { - PrevStage::revFlowAp(node, apa, config) - } - - pragma[nomagic] - private predicate flowIntoCallApa( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa, - Configuration config - ) { - flowIntoCall(call, arg, p, allowsFieldFlow, config) and - PrevStage::revFlowAp(p, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(arg, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowOutOfCallApa( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - ApApprox apa, Configuration config - ) { - flowOutOfCall(call, ret, kind, out, allowsFieldFlow, config) and - PrevStage::revFlowAp(out, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(ret, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowThroughOutOfCall( - DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, - ApApprox argApa, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa, pragma[only_bind_into](config)) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) and - matchesCall(ccc, call) - ) - } - - /** - * Holds if `node` is reachable with access path `ap` from a source in the - * configuration `config`. - * - * The call context `cc` records whether the node is reached through an - * argument in a call, and if so, `summaryCtx` and `argAp` record the - * corresponding parameter position and access path of that argument, respectively. - */ - pragma[nomagic] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa, config) and - PrevStage::revFlow(node, state, apa, config) and - filter(node, state, ap, config) - } - - pragma[inline] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, cc, summaryCtx, argAp, ap, _, config) - } - - pragma[nomagic] - private predicate fwdFlow0( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - sourceNode(node, state, config) and - (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and - argAp = apNone() and - summaryCtx = TParamNodeNone() and - ap = getApNil(node) and - apa = getApprox(ap) - or - exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | - fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0, config) and - localCc = getLocalCc(mid, cc) - | - localStep(mid, state0, node, state, true, _, config, localCc) and - ap = ap0 and - apa = apa0 - or - localStep(mid, state0, node, state, false, ap, config, localCc) and - ap0 instanceof ApNil and - apa = getApprox(ap) - ) - or - exists(NodeEx mid | - fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa, pragma[only_bind_into](config)) and - jumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(mid, state, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(mid, state0, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStateStep(mid, state0, node, state, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - // store - exists(TypedContent tc, Ap ap0 | - fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp, config) and - ap = apCons(tc, ap0) and - apa = getApprox(ap) - ) - or - // read - exists(Ap ap0, Content c | - fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp, config) and - fwdFlowConsCand(ap0, c, ap, config) and - apa = getApprox(ap) - ) - or - // flow into a callable - fwdFlowIn(_, node, state, _, cc, _, _, ap, apa, config) and - if PrevStage::parameterMayFlowThrough(node, apa, config) - then ( - summaryCtx = TParamNodeSome(node.asNode()) and - argAp = apSome(ap) - ) else ( - summaryCtx = TParamNodeNone() and argAp = apNone() - ) - or - // flow out of a callable - exists( - DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, - DataFlowCallable inner - | - fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa, config) and - flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa, config) and - inner = ret.getEnclosingCallable() and - cc = getCallContextReturn(inner, call, innercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - or - // flow through a callable - exists( - DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, ApApprox innerArgApa - | - fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowStore( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(DataFlowType contentType, ApApprox apa1 | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1, config) and - PrevStage::storeStepCand(node1, apa1, tc, node2, contentType, config) and - typecheckStore(ap1, contentType) - ) - } - - /** - * Holds if forward flow with access path `tail` reaches a store of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(TypedContent tc | - fwdFlowStore(_, tail, tc, _, _, _, _, _, config) and - tc.getContent() = c and - cons = apCons(tc, tail) - ) - } - - pragma[nomagic] - private predicate readStepCand( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - PrevStage::readStepCand(node1, c, node2, config) and - apc = projectToHeadContent(c) - } - - bindingset[node1, apc] - pragma[inline_late] - private predicate readStepCand0( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - readStepCand(node1, apc, c, node2, config) - } - - pragma[nomagic] - private predicate fwdFlowRead( - Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(ApHeadContent apc | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap, config) and - apc = getHeadContent(ap) and - readStepCand0(node1, apc, c, node2, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowIn( - DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, - ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa, Configuration config - ) { - exists(ArgNodeEx arg, boolean allowsFieldFlow | - fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa, config) and - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowRetFromArg( - RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, ApApprox argApa, - Ap ap, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - fwdFlow(pragma[only_bind_into](ret), state, ccc, - TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), - pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa), - pragma[only_bind_into](config)) and - kind = ret.getKind() and - parameterFlowThroughAllowed(summaryCtx, kind) and - argApa = getApprox(argAp) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) - ) - } - - pragma[inline] - private predicate fwdFlowThrough0( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, - Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa, config) and - fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp, config) - } - - pragma[nomagic] - private predicate fwdFlowThrough( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa, - config) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` - * and data might flow through the target callable and back out at `call`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered( - DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, - ParamNodeEx p, Ap ap, Configuration config - ) { - exists(ApApprox apa | - fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(p, apa, config) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate storeStepFwd( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config - ) { - fwdFlowStore(node1, ap1, tc, node2, _, _, _, _, config) and - ap2 = apCons(tc, ap1) and - fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _, config) - } - - private predicate readStepFwd( - NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config - ) { - fwdFlowRead(ap1, c, n1, n2, _, _, _, _, config) and - fwdFlowConsCand(ap1, c, ap2, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough0( - DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, - ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, - innerArgApa, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough( - RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, - Ap ap, Configuration config - ) { - exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | - returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa, config) and - pos = ret.getReturnPosition() and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowThroughIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap, - Configuration config - ) { - exists(ApApprox argApa | - flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), - allowsFieldFlow, argApa, pragma[only_bind_into](config)) and - fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa, - pragma[only_bind_into](config)) and - returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap, - pragma[only_bind_into](config)) and - if allowsFieldFlow = false then argAp instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowIntoCallAp( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap, - Configuration config - ) { - exists(ApApprox apa | - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - fwdFlow(arg, _, _, _, _, ap, apa, config) - ) - } - - pragma[nomagic] - private predicate flowOutOfCallAp( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, - Ap ap, Configuration config - ) { - exists(ApApprox apa | - flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa, config) and - fwdFlow(ret, _, _, _, _, ap, apa, config) and - pos = ret.getReturnPosition() - ) - } - - /** - * Holds if `node` with access path `ap` is part of a path from a source to a - * sink in the configuration `config`. - * - * The parameter `returnCtx` records whether (and how) the node must be returned - * from the enclosing callable in order to reach a sink, and if so, `returnAp` - * records the access path of the returned value. - */ - pragma[nomagic] - additional predicate revFlow( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - revFlow0(node, state, returnCtx, returnAp, ap, config) and - fwdFlow(node, state, _, _, _, ap, config) - } - - pragma[nomagic] - private predicate revFlow0( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, _, _, _, ap, config) and - sinkNode(node, state, config) and - ( - if hasSinkCallCtx(config) - then returnCtx = TReturnCtxNoFlowThrough() - else returnCtx = TReturnCtxNone() - ) and - returnAp = apNone() and - ap instanceof ApNil - or - exists(NodeEx mid, FlowState state0 | - localStep(node, state, mid, state0, true, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, ap, config) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap, pragma[only_bind_into](config)) and - localStep(node, pragma[only_bind_into](state), mid, state0, false, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, nil, pragma[only_bind_into](config)) and - ap instanceof ApNil - ) - or - exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, state, _, _, ap, config) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStep(node, mid, config) and - revFlow(pragma[only_bind_into](mid), state, _, _, nil, pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStateStep(node, state, mid, state0, config) and - revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil, - pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - // store - exists(Ap ap0, Content c | - revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp, config) and - revFlowConsCand(ap0, c, ap, config) - ) - or - // read - exists(NodeEx mid, Ap ap0 | - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - readStepFwd(node, ap, _, mid, ap0, config) - ) - or - // flow into a callable - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, state, TReturnCtxNone(), returnAp, ap, config) and - flowIntoCallAp(_, node, p, allowsFieldFlow, ap, config) and - (if allowsFieldFlow = false then ap instanceof ApNil else any()) and - returnCtx = TReturnCtxNone() - ) - or - // flow through a callable - exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, node, p, _, ap, innerReturnAp, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(_, node, pos, state, _, _, ap, config) and - if returnFlowsThrough(node, pos, state, _, _, _, ap, config) - then ( - returnCtx = TReturnCtxMaybeFlowThrough(pos) and - returnAp = apSome(ap) - ) else ( - returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() - ) - ) - } - - pragma[nomagic] - private predicate revFlowStore( - Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, - ReturnCtx returnCtx, ApOption returnAp, Configuration config - ) { - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - storeStepFwd(node, ap, tc, mid, ap0, config) and - tc.getContent() = c - } - - /** - * Holds if reverse flow with access path `tail` reaches a read of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(NodeEx mid, Ap tail0 | - revFlow(mid, _, _, _, tail, config) and - tail = pragma[only_bind_into](tail0) and - readStepFwd(_, cons, c, mid, tail0, config) - ) - } - - pragma[nomagic] - private predicate revFlowOut( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, - ApOption returnAp, Ap ap, Configuration config - ) { - exists(NodeEx out, boolean allowsFieldFlow | - revFlow(out, state, returnCtx, returnAp, ap, config) and - flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate revFlowParamToReturn( - ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap, Configuration config - ) { - revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), - pragma[only_bind_into](ap), pragma[only_bind_into](config)) and - parameterFlowThroughAllowed(p, pos.getKind()) and - PrevStage::parameterMayFlowThrough(p, getApprox(ap), config) - } - - pragma[nomagic] - private predicate revFlowThrough( - DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, - ApOption returnAp, Ap ap, Ap innerReturnAp, Configuration config - ) { - revFlowParamToReturn(p, state, pos, innerReturnAp, ap, config) and - revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned( - DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap, - Configuration config - ) { - exists(RetNodeEx ret, FlowState state, CcCall ccc | - revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap, config) and - returnFlowsThrough(ret, pos, state, ccc, _, _, ap, config) and - matchesCall(ccc, call) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Ap ap2, Content c | - PrevStage::storeStepCand(node1, _, tc, node2, contentType, config) and - revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _, config) and - revFlowConsCand(ap2, c, ap1, config) - ) - } - - predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(Ap ap1, Ap ap2 | - revFlow(node2, _, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and - readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _, - pragma[only_bind_into](config)) - ) - } - - additional predicate revFlow(NodeEx node, FlowState state, Configuration config) { - revFlow(node, state, _, _, _, config) - } - - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, _, _, ap, config) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, _, _, _, ap, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, Configuration config) { - revFlow(node, _, _, _, _, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, ap, config) - } - - private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepFwd(_, ap, tc, _, _, config) - } - - private predicate revConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepCand(_, ap, tc, _, _, config) - } - - private predicate validAp(Ap ap, Configuration config) { - revFlow(_, _, _, _, ap, config) and ap instanceof ApNil - or - exists(TypedContent head, Ap tail | - consCand(head, tail, config) and - ap = apCons(head, tail) - ) - } - - additional predicate consCand(TypedContent tc, Ap ap, Configuration config) { - revConsCand(tc, ap, config) and - validAp(ap, config) - } - - pragma[nomagic] - private predicate parameterFlowsThroughRev( - ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp, Configuration config - ) { - revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap, config) and - parameterFlowThroughAllowed(p, pos.getKind()) - } - - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(ReturnPosition pos | - returnFlowsThrough(_, pos, _, _, p, ap, _, config) and - parameterFlowsThroughRev(p, ap, pos, _, config) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - exists(ParamNodeEx p, ReturnPosition pos | - returnFlowsThrough(ret, pos, _, _, p, argAp, ap, config) and - parameterFlowsThroughRev(p, argAp, pos, ap, config) and - kind = pos.getKind() - ) - } - - pragma[nomagic] - private predicate revFlowThroughArg( - DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, - Ap ap, Configuration config - ) { - exists(ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp, config) - ) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | - revFlow(arg, state, returnCtx, returnAp, ap, config) and - revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, config)) and - fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and - states = count(FlowState state | fwdFlow(_, state, _, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | - fwdFlow(n, state, cc, summaryCtx, argAp, ap, config) - ) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, _, _, _, config)) and - fields = count(TypedContent f0 | consCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and - states = count(FlowState state | revFlow(_, state, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | - revFlow(n, state, returnCtx, retAp, ap, config) - ) - } - /* End: Stage logic. */ - } -} - -private module BooleanCallContext { - class Cc extends boolean { - Cc() { this in [true, false] } - } - - class CcCall extends Cc { - CcCall() { this = true } - } - - /** Holds if the call context may be `call`. */ - predicate matchesCall(CcCall cc, DataFlowCall call) { any() } - - class CcNoCall extends Cc { - CcNoCall() { this = false } - } - - Cc ccNone() { result = false } - - CcCall ccSomeCall() { result = true } - - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } -} - -private module Level1CallContext { - class Cc = CallContext; - - class CcCall = CallContextCall; - - pragma[inline] - predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } - - class CcNoCall = CallContextNoCall; - - Cc ccNone() { result instanceof CallContextAny } - - CcCall ccSomeCall() { result instanceof CallContextSomeCall } - - module NoLocalCallContext { - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSiteDispatch(call, c) - then result = TSpecificCall(call) - else result = TSomeCall() - } - } - - module LocalCallContext { - class LocalCc = LocalCallContext; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { - result = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - node.getEnclosingCallable()) - } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() - } - } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { - checkCallContextReturn(innercc, c, call) and - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() - } -} - -private module Stage2Param implements MkStage::StageParam { - private module PrevStage = Stage1; - - class Ap extends boolean { - Ap() { this in [true, false] } - } - - class ApNil extends Ap { - ApNil() { this = false } - } - - bindingset[result, ap] - PrevStage::Ap getApprox(Ap ap) { any() } - - ApNil getApNil(NodeEx node) { Stage1::revFlow(node, _) and exists(result) } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } - - class ApHeadContent = Unit; - - pragma[inline] - ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } - - ApHeadContent projectToHeadContent(Content c) { any() } - - class ApOption = BooleanOption; - - ApOption apNone() { result = TBooleanNone() } - - ApOption apSome(Ap ap) { result = TBooleanSome(ap) } - - import Level1CallContext - import NoLocalCallContext - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - ( - preservesValue = true and - localFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalStateStep(node1, state1, node2, state2, config) - ) and - exists(ap) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand1/6; - - predicate flowIntoCall = flowIntoCallNodeCand1/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - expectsContentEx(node, c) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - PrevStage::revFlowState(state, pragma[only_bind_into](config)) and - exists(ap) and - not stateBarrier(node, state, config) and - ( - notExpectsContent(node) - or - ap = true and - expectsContentCand(node, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage2 implements StageSig { - import MkStage::Stage -} - -pragma[nomagic] -private predicate flowOutOfCallNodeCand2( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -pragma[nomagic] -private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -private module LocalFlowBigStep { - /** - * A node where some checking is required, and hence the big-step relation - * is not allowed to step over. - */ - private class FlowCheckNode extends NodeEx { - FlowCheckNode() { - castNode(this.asNode()) or - clearsContentCached(this.asNode(), _) or - expectsContentCached(this.asNode(), _) - } - } - - /** - * Holds if `node` can be the first node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowEntry(NodeEx node, FlowState state, Configuration config) { - Stage2::revFlow(node, state, config) and - ( - sourceNode(node, state, config) - or - jumpStep(_, node, config) - or - additionalJumpStep(_, node, config) - or - additionalJumpStateStep(_, _, node, state, config) - or - node instanceof ParamNodeEx - or - node.asNode() instanceof OutNodeExt - or - Stage2::storeStepCand(_, _, _, node, _, config) - or - Stage2::readStepCand(_, _, node, config) - or - node instanceof FlowCheckNode - or - exists(FlowState s | - additionalLocalStateStep(_, s, node, state, config) and - s != state - ) - ) - } - - /** - * Holds if `node` can be the last node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowExit(NodeEx node, FlowState state, Configuration config) { - exists(NodeEx next | Stage2::revFlow(next, state, config) | - jumpStep(node, next, config) or - additionalJumpStep(node, next, config) or - flowIntoCallNodeCand2(_, node, next, _, config) or - flowOutOfCallNodeCand2(_, node, _, next, _, config) or - Stage2::storeStepCand(node, _, _, next, _, config) or - Stage2::readStepCand(node, _, next, config) - ) - or - exists(NodeEx next, FlowState s | Stage2::revFlow(next, s, config) | - additionalJumpStateStep(node, state, next, s, config) - or - additionalLocalStateStep(node, state, next, s, config) and - s != state - ) - or - Stage2::revFlow(node, state, config) and - node instanceof FlowCheckNode - or - sinkNode(node, state, config) - } - - pragma[noinline] - private predicate additionalLocalFlowStepNodeCand2( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, Configuration config - ) { - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 and - Stage2::revFlow(node1, pragma[only_bind_into](state1), false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false, - pragma[only_bind_into](config)) - or - additionalLocalStateStep(node1, state1, node2, state2, config) and - Stage2::revFlow(node1, state1, false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, state2, false, pragma[only_bind_into](config)) - } - - /** - * Holds if the local path from `node1` to `node2` is a prefix of a maximal - * subsequence of local flow steps in a dataflow path. - * - * This is the transitive closure of `[additional]localFlowStep` beginning - * at `localFlowEntry`. - */ - pragma[nomagic] - private predicate localFlowStepPlus( - NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, - Configuration config, LocalCallContext cc - ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and - ( - localFlowEntry(node1, pragma[only_bind_into](state), pragma[only_bind_into](config)) and - ( - localFlowStepNodeCand1(node1, node2, config) and - preservesValue = true and - t = node1.getDataFlowType() and // irrelevant dummy value - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - or - additionalLocalFlowStepNodeCand2(node1, state, node2, state, config) and - preservesValue = false and - t = node2.getDataFlowType() - ) and - node1 != node2 and - cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) - or - exists(NodeEx mid | - localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, - pragma[only_bind_into](config), cc) and - localFlowStepNodeCand1(mid, node2, config) and - not mid instanceof FlowCheckNode and - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - ) - or - exists(NodeEx mid | - localFlowStepPlus(node1, state, mid, _, _, pragma[only_bind_into](config), cc) and - additionalLocalFlowStepNodeCand2(mid, state, node2, state, config) and - not mid instanceof FlowCheckNode and - preservesValue = false and - t = node2.getDataFlowType() - ) - ) - } - - /** - * Holds if `node1` can step to `node2` in one or more local steps and this - * path can occur as a maximal subsequence of local steps in a dataflow path. - */ - pragma[nomagic] - predicate localFlowBigStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - DataFlowType t, Configuration config, LocalCallContext callContext - ) { - localFlowStepPlus(node1, state1, node2, preservesValue, t, config, callContext) and - localFlowExit(node2, state1, config) and - state1 = state2 - or - additionalLocalFlowStepNodeCand2(node1, state1, node2, state2, config) and - state1 != state2 and - preservesValue = false and - t = node2.getDataFlowType() and - callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) - } -} - -private import LocalFlowBigStep - -private module Stage3Param implements MkStage::StageParam { - private module PrevStage = Stage2; - - class Ap = ApproxAccessPathFront; - - class ApNil = ApproxAccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } - - class ApHeadContent = ContentApprox; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - predicate projectToHeadContent = getContentApprox/1; - - class ApOption = ApproxAccessPathFrontOption; - - ApOption apNone() { result = TApproxAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } - - import BooleanCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApproxAccessPathFrontNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand2/6; - - predicate flowIntoCall = flowIntoCallNodeCand2/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getAHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage3 implements StageSig { - import MkStage::Stage -} - -private module Stage4Param implements MkStage::StageParam { - private module PrevStage = Stage3; - - class Ap = AccessPathFront; - - class ApNil = AccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathFrontOption; - - ApOption apNone() { result = TAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } - - import BooleanCallContext - - pragma[nomagic] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) and - exists(lcc) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clearSet(NodeEx node, ContentSet c, Configuration config) { - PrevStage::revFlow(node, config) and - clearsContentCached(node.asNode(), c) - } - - pragma[nomagic] - private predicate clearContent(NodeEx node, Content c, Configuration config) { - exists(ContentSet cs | - PrevStage::readStepCand(_, pragma[only_bind_into](c), _, pragma[only_bind_into](config)) and - c = cs.getAReadContent() and - clearSet(node, cs, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clear(NodeEx node, Ap ap, Configuration config) { - clearContent(node, ap.getHead().getContent(), config) - } - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - not clear(node, ap, config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage4 implements StageSig { - import MkStage::Stage -} - -/** - * Holds if `argApf` is recorded as the summary context for flow reaching `node` - * and remains relevant for the following pruning stage. - */ -private predicate flowCandSummaryCtx( - NodeEx node, FlowState state, AccessPathFront argApf, Configuration config -) { - exists(AccessPathFront apf | - Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf, config) and - Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf, - config) - ) -} - -/** - * Holds if a length 2 access path approximation with the head `tc` is expected - * to be expensive. - */ -private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { - exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf, config)) and - nodes = - strictcount(NodeEx n, FlowState state | - Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - or - flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - ) and - accessPathApproxCostLimits(apLimit, tupleLimit) and - apLimit < tails and - tupleLimit < (tails - 1) * nodes and - not tc.forceHighPrecision() - ) -} - -private newtype TAccessPathApprox = - TNil(DataFlowType t) or - TConsNil(TypedContent tc, DataFlowType t) { - Stage4::consCand(tc, TFrontNil(t), _) and - not expensiveLen2unfolding(tc, _) - } or - TConsCons(TypedContent tc1, TypedContent tc2, int len) { - Stage4::consCand(tc1, TFrontHead(tc2), _) and - len in [2 .. accessPathLimit()] and - not expensiveLen2unfolding(tc1, _) - } or - TCons1(TypedContent tc, int len) { - len in [1 .. accessPathLimit()] and - expensiveLen2unfolding(tc, _) - } - -/** - * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only - * the first two elements of the list and its length are tracked. If data flows - * from a source to a given node with a given `AccessPathApprox`, this indicates - * the sequence of dereference operations needed to get from the value in the node - * to the tracked object. The final type indicates the type of the tracked object. - */ -abstract private class AccessPathApprox extends TAccessPathApprox { - abstract string toString(); - - abstract TypedContent getHead(); - - abstract int len(); - - abstract DataFlowType getType(); - - abstract AccessPathFront getFront(); - - /** Gets the access path obtained by popping `head` from this path, if any. */ - abstract AccessPathApprox pop(TypedContent head); -} - -private class AccessPathApproxNil extends AccessPathApprox, TNil { - private DataFlowType t; - - AccessPathApproxNil() { this = TNil(t) } - - override string toString() { result = concat(": " + ppReprType(t)) } - - override TypedContent getHead() { none() } - - override int len() { result = 0 } - - override DataFlowType getType() { result = t } - - override AccessPathFront getFront() { result = TFrontNil(t) } - - override AccessPathApprox pop(TypedContent head) { none() } -} - -abstract private class AccessPathApproxCons extends AccessPathApprox { } - -private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { - private TypedContent tc; - private DataFlowType t; - - AccessPathApproxConsNil() { this = TConsNil(tc, t) } - - override string toString() { - // The `concat` becomes "" if `ppReprType` has no result. - result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) - } - - override TypedContent getHead() { result = tc } - - override int len() { result = 1 } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } -} - -private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { - private TypedContent tc1; - private TypedContent tc2; - private int len; - - AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - - override string toString() { - if len = 2 - then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" - else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc1 } - - override int len() { result = len } - - override DataFlowType getType() { result = tc1.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc1) } - - override AccessPathApprox pop(TypedContent head) { - head = tc1 and - ( - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - } -} - -private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { - private TypedContent tc; - private int len; - - AccessPathApproxCons1() { this = TCons1(tc, len) } - - override string toString() { - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc } - - override int len() { result = len } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { - head = tc and - ( - exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2), _) | - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - or - exists(DataFlowType t | - len = 1 and - Stage4::consCand(tc, TFrontNil(t), _) and - result = TNil(t) - ) - ) - } -} - -/** Gets the access path obtained by popping `tc` from `ap`, if any. */ -private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } - -/** Gets the access path obtained by pushing `tc` onto `ap`. */ -private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } - -private newtype TAccessPathApproxOption = - TAccessPathApproxNone() or - TAccessPathApproxSome(AccessPathApprox apa) - -private class AccessPathApproxOption extends TAccessPathApproxOption { - string toString() { - this = TAccessPathApproxNone() and result = "" - or - this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) - } -} - -private module Stage5Param implements MkStage::StageParam { - private module PrevStage = Stage4; - - class Ap = AccessPathApprox; - - class ApNil = AccessPathApproxNil; - - pragma[nomagic] - PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathApproxOption; - - ApOption apNone() { result = TAccessPathApproxNone() } - - ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } - - import Level1CallContext - import LocalCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, lcc) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { any() } - - // Type checking is not necessary here as it has already been done in stage 3. - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage5 = MkStage::Stage; - -bindingset[conf, result] -private Configuration unbindConf(Configuration conf) { - exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c)) -} - -pragma[nomagic] -private predicate nodeMayUseSummary0( - NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(AccessPathApprox apa0 | - Stage5::parameterMayFlowThrough(p, _, _) and - Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0, config) and - Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), - TAccessPathApproxSome(apa), apa0, config) - ) -} - -pragma[nomagic] -private predicate nodeMayUseSummary( - NodeEx n, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::parameterMayFlowThrough(p, apa, config) and - nodeMayUseSummary0(n, p, state, apa, config) - ) -} - -private newtype TSummaryCtx = - TSummaryCtxNone() or - TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { - exists(Configuration config | - Stage5::parameterMayFlowThrough(p, ap.getApprox(), config) and - Stage5::revFlow(p, state, _, config) - ) - } - -/** - * A context for generating flow summaries. This represents flow entry through - * a specific parameter with an access path of a specific shape. - * - * Summaries are only created for parameters that may flow through. - */ -abstract private class SummaryCtx extends TSummaryCtx { - abstract string toString(); -} - -/** A summary context from which no flow summary can be generated. */ -private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { - override string toString() { result = "" } -} - -/** A summary context from which a flow summary can be generated. */ -private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParamNodeEx p; - private FlowState s; - private AccessPath ap; - - SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } - - ParameterPosition getParameterPos() { p.isParameterOf(_, result) } - - ParamNodeEx getParamNode() { result = p } - - override string toString() { result = p + ": " + ap } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** - * Gets the number of length 2 access path approximations that correspond to `apa`. - */ -private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { - exists(TypedContent tc, int len | - tc = apa.getHead() and - len = apa.len() and - result = - strictcount(AccessPathFront apf | - Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), - config) - ) - ) -} - -private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = - strictcount(NodeEx n, FlowState state | - Stage5::revFlow(n, state, apa, config) or nodeMayUseSummary(n, state, apa, config) - ) -} - -/** - * Holds if a length 2 access path approximation matching `apa` is expected - * to be expensive. - */ -private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = count1to2unfold(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - apLimit < aps and - tupleLimit < (aps - 1) * nodes - ) -} - -private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { - exists(TypedContent head | - apa.pop(head) = result and - Stage5::consCand(head, result, config) - ) -} - -/** - * Holds with `unfold = false` if a precise head-tail representation of `apa` is - * expected to be expensive. Holds with `unfold = true` otherwise. - */ -private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { - if apa.getHead().forceHighPrecision() - then unfold = true - else - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = countPotentialAps(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true - ) -} - -/** - * Gets the number of `AccessPath`s that correspond to `apa`. - */ -pragma[assume_small_delta] -private int countAps(AccessPathApprox apa, Configuration config) { - evalUnfold(apa, false, config) and - result = 1 and - (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) - or - evalUnfold(apa, false, config) and - result = count1to2unfold(apa, config) and - not expensiveLen1to2unfolding(apa, config) - or - evalUnfold(apa, true, config) and - result = countPotentialAps(apa, config) -} - -/** - * Gets the number of `AccessPath`s that would correspond to `apa` assuming - * that it is expanded to a precise head-tail representation. - */ -language[monotonicAggregates] -pragma[assume_small_delta] -private int countPotentialAps(AccessPathApprox apa, Configuration config) { - apa instanceof AccessPathApproxNil and result = 1 - or - result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) -} - -private newtype TAccessPath = - TAccessPathNil(DataFlowType t) or - TAccessPathCons(TypedContent head, AccessPath tail) { - exists(AccessPathApproxCons apa | - not evalUnfold(apa, false, _) and - head = apa.getHead() and - tail.getApprox() = getATail(apa, _) - ) - } or - TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - not expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head1 = apa.getHead() and - head2 = getATail(apa, _).getHead() - ) - } or - TAccessPathCons1(TypedContent head, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head = apa.getHead() - ) - } - -private newtype TPathNode = - pragma[assume_small_delta] - TPathNodeMid( - NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config - ) { - // A PathNode is introduced by a source ... - Stage5::revFlow(node, state, config) and - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - or - // ... or a step from an existing PathNode to another node. - exists(PathNodeMid mid | - pathStep(mid, node, state, cc, sc, ap) and - pragma[only_bind_into](config) = mid.getConfiguration() and - Stage5::revFlow(node, state, ap.getApprox(), pragma[only_bind_into](config)) - ) - } or - TPathNodeSink(NodeEx node, FlowState state, Configuration config) { - exists(PathNodeMid sink | - sink.isAtSink() and - node = sink.getNodeEx() and - state = sink.getState() and - config = sink.getConfiguration() - ) - } or - TPathNodeSourceGroup(string sourceGroup, Configuration config) { - exists(PathNodeImpl source | - sourceGroup = source.getSourceGroup() and - config = source.getConfiguration() - ) - } or - TPathNodeSinkGroup(string sinkGroup, Configuration config) { - exists(PathNodeSink sink | - sinkGroup = sink.getSinkGroup() and - config = sink.getConfiguration() - ) - } - -/** - * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a - * source to a given node with a given `AccessPath`, this indicates the sequence - * of dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ -private class AccessPath extends TAccessPath { - /** Gets the head of this access path, if any. */ - abstract TypedContent getHead(); - - /** Gets the tail of this access path, if any. */ - abstract AccessPath getTail(); - - /** Gets the front of this access path. */ - abstract AccessPathFront getFront(); - - /** Gets the approximation of this access path. */ - abstract AccessPathApprox getApprox(); - - /** Gets the length of this access path. */ - abstract int length(); - - /** Gets a textual representation of this access path. */ - abstract string toString(); - - /** Gets the access path obtained by popping `tc` from this access path, if any. */ - final AccessPath pop(TypedContent tc) { - result = this.getTail() and - tc = this.getHead() - } - - /** Gets the access path obtained by pushing `tc` onto this access path. */ - final AccessPath push(TypedContent tc) { this = result.pop(tc) } -} - -private class AccessPathNil extends AccessPath, TAccessPathNil { - private DataFlowType t; - - AccessPathNil() { this = TAccessPathNil(t) } - - DataFlowType getType() { result = t } - - override TypedContent getHead() { none() } - - override AccessPath getTail() { none() } - - override AccessPathFrontNil getFront() { result = TFrontNil(t) } - - override AccessPathApproxNil getApprox() { result = TNil(t) } - - override int length() { result = 0 } - - override string toString() { result = concat(": " + ppReprType(t)) } -} - -private class AccessPathCons extends AccessPath, TAccessPathCons { - private TypedContent head; - private AccessPath tail; - - AccessPathCons() { this = TAccessPathCons(head, tail) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { result = tail } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - pragma[assume_small_delta] - override AccessPathApproxCons getApprox() { - result = TConsNil(head, tail.(AccessPathNil).getType()) - or - result = TConsCons(head, tail.getHead(), this.length()) - or - result = TCons1(head, this.length()) - } - - pragma[assume_small_delta] - override int length() { result = 1 + tail.length() } - - private string toStringImpl(boolean needsSuffix) { - exists(DataFlowType t | - tail = TAccessPathNil(t) and - needsSuffix = false and - result = head.toString() + "]" + concat(" : " + ppReprType(t)) - ) - or - result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) - or - exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | - result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true - or - result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false - ) - or - exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | - result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true - or - result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false - ) - } - - override string toString() { - result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" - or - result = "[" + this.toStringImpl(false) - } -} - -private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { - private TypedContent head1; - private TypedContent head2; - private int len; - - AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } - - override TypedContent getHead() { result = head1 } - - override AccessPath getTail() { - Stage5::consCand(head1, result.getApprox(), _) and - result.getHead() = head2 and - result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head1) } - - override AccessPathApproxCons getApprox() { - result = TConsCons(head1, head2, len) or - result = TCons1(head1, len) - } - - override int length() { result = len } - - override string toString() { - if len = 2 - then result = "[" + head1.toString() + ", " + head2.toString() + "]" - else - result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" - } -} - -private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { - private TypedContent head; - private int len; - - AccessPathCons1() { this = TAccessPathCons1(head, len) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { - Stage5::consCand(head, result.getApprox(), _) and result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - - override int length() { result = len } - - override string toString() { - if len = 1 - then result = "[" + head.toString() + "]" - else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" - } -} - -abstract private class PathNodeImpl extends TPathNode { - /** Gets the `FlowState` of this node. */ - abstract FlowState getState(); - - /** Gets the associated configuration. */ - abstract Configuration getConfiguration(); - - /** Holds if this node is a source. */ - abstract predicate isSource(); - - abstract PathNodeImpl getASuccessorImpl(); - - private PathNodeImpl getASuccessorIfHidden() { - this.isHidden() and - result = this.getASuccessorImpl() - } - - pragma[nomagic] - private PathNodeImpl getANonHiddenSuccessor0() { - result = this.getASuccessorIfHidden*() and - not result.isHidden() - } - - final PathNodeImpl getANonHiddenSuccessor() { - result = this.getASuccessorImpl().getANonHiddenSuccessor0() and - not this.isHidden() - } - - abstract NodeEx getNodeEx(); - - predicate isHidden() { - not this.getConfiguration().includeHiddenNodes() and - ( - hiddenNode(this.getNodeEx().asNode()) and - not this.isSource() and - not this instanceof PathNodeSink - or - this.getNodeEx() instanceof TNodeImplicitRead - ) - } - - string getSourceGroup() { - this.isSource() and - this.getConfiguration().sourceGrouping(this.getNodeEx().asNode(), result) + not singleConfiguration() and + getConfig(state1).isAdditionalFlowStep(node1, node2) and + state2 = state1 } - predicate isFlowSource() { - this.isSource() and not exists(this.getSourceGroup()) - or - this instanceof PathNodeSourceGroup + predicate allowImplicitRead(Node node, ContentSet c) { + any(Configuration config).allowImplicitRead(node, c) } - predicate isFlowSink() { - this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or - this instanceof PathNodeSinkGroup - } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } - private string ppAp() { - this instanceof PathNodeSink and result = "" - or - exists(string s | s = this.(PathNodeMid).getAp().toString() | - if s = "" then result = "" else result = " " + s - ) - } + FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } - private string ppCtx() { - this instanceof PathNodeSink and result = "" - or - result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + predicate sourceGrouping(Node source, string sourceGroup) { + any(Configuration config).sourceGrouping(source, sourceGroup) } - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + predicate sinkGrouping(Node sink, string sinkGroup) { + any(Configuration config).sinkGrouping(sink, sinkGroup) } -} - -/** Holds if `n` can reach a sink. */ -private predicate directReach(PathNodeImpl n) { - n instanceof PathNodeSink or - n instanceof PathNodeSinkGroup or - directReach(n.getANonHiddenSuccessor()) -} - -/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ -private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } -/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { - n1.getANonHiddenSuccessor() = n2 and directReach(n2) + predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() } } -private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) +private import Impl as I +import I /** * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. */ -class PathNode instanceof PathNodeImpl { - PathNode() { reach(this) } - +class PathNode instanceof I::PathNode { /** Gets a textual representation of this element. */ final string toString() { result = super.toString() } @@ -3406,1548 +358,39 @@ class PathNode instanceof PathNodeImpl { } /** Gets the underlying `Node`. */ - final Node getNode() { super.getNodeEx().projectToNode() = result } + final Node getNode() { result = super.getNode() } /** Gets the `FlowState` of this node. */ - final FlowState getState() { result = super.getState() } + final FlowState getState() { result = getState(super.getState()) } /** Gets the associated configuration. */ - final Configuration getConfiguration() { result = super.getConfiguration() } + final Configuration getConfiguration() { result = getConfig(super.getState()) } /** Gets a successor of this node, if any. */ - final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } + final PathNode getASuccessor() { result = super.getASuccessor() } /** Holds if this node is a source. */ final predicate isSource() { super.isSource() } /** Holds if this node is a grouping of source nodes. */ - final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group, _) } + final predicate isSourceGroup(string group) { super.isSourceGroup(group) } /** Holds if this node is a grouping of sink nodes. */ - final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group, _) } + final predicate isSinkGroup(string group) { super.isSinkGroup(group) } } -/** - * Provides the query predicates needed to include a graph in a path-problem query. - */ -module PathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } - - /** Holds if `n` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode n, string key, string val) { - key = "semmle.label" and val = n.toString() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { - Subpaths::subpaths(arg, par, ret, out) - } -} - -/** - * An intermediate flow graph node. This is a triple consisting of a `Node`, - * a `CallContext`, and a `Configuration`. - */ -private class PathNodeMid extends PathNodeImpl, TPathNodeMid { - NodeEx node; - FlowState state; - CallContext cc; - SummaryCtx sc; - AccessPath ap; - Configuration config; - - PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - SummaryCtx getSummaryCtx() { result = sc } - - AccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - private PathNodeMid getSuccMid() { - pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx(), result.getAp()) and - result.getConfiguration() = unbindConf(this.getConfiguration()) - } - - override PathNodeImpl getASuccessorImpl() { - // an intermediate step to another intermediate node - result = this.getSuccMid() - or - // a final step to a sink - result = this.getSuccMid().projectToSink() - } - - override predicate isSource() { - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - } - - predicate isAtSink() { - sinkNode(node, state, config) and - ap instanceof AccessPathNil and - if hasSinkCallCtx(config) - then - // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` - // is exactly what we need to check. This also implies - // `sc instanceof SummaryCtxNone`. - // For `FeatureEqualSourceSinkCallContext` the initial call context was - // set to `CallContextSomeCall` and jumps are disallowed, so - // `cc instanceof CallContextNoCall` never holds. On the other hand, - // in this case there's never any need to enter a call except to identify - // a summary, so the condition in `pathIntoCallable` enforces this, which - // means that `sc instanceof SummaryCtxNone` holds if and only if we are - // in the call context of the source. - sc instanceof SummaryCtxNone or - cc instanceof CallContextNoCall - else any() - } - - PathNodeSink projectToSink() { - this.isAtSink() and - result.getNodeEx() = node and - result.getState() = state and - result.getConfiguration() = unbindConf(config) - } -} - -/** - * A flow graph node corresponding to a sink. This is disjoint from the - * intermediate nodes in order to uniquely correspond to a given sink by - * excluding the `CallContext`. - */ -private class PathNodeSink extends PathNodeImpl, TPathNodeSink { - NodeEx node; - FlowState state; - Configuration config; - - PathNodeSink() { this = TPathNodeSink(node, state, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result = TPathNodeSinkGroup(this.getSinkGroup(), config) - } - - override predicate isSource() { sourceNode(node, state, config) } - - string getSinkGroup() { config.sinkGrouping(node.asNode(), result) } -} - -private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { - string sourceGroup; - Configuration config; - - PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result.getSourceGroup() = sourceGroup and - result.getConfiguration() = config - } - - override predicate isSource() { none() } - - override string toString() { result = sourceGroup } - - override 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 - } -} - -private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { - string sinkGroup; - Configuration config; - - PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { none() } - - override predicate isSource() { none() } - - override string toString() { result = sinkGroup } - - override 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 - } -} - -private predicate pathNode( - PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, - Configuration conf, LocalCallContext localCC -) { - midnode = mid.getNodeEx() and - state = mid.getState() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - midnode.getEnclosingCallable()) and - ap = mid.getAp() -} - -/** - * Holds if data may flow from `mid` to `node`. The last step in or out of - * a callable is recorded by `cc`. - */ -pragma[assume_small_delta] -pragma[nomagic] -private predicate pathStep( - PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap -) { - exists(NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC | - pathNode(mid, midnode, state0, cc, sc, ap, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, true, _, conf, localCC) - ) - or - exists( - AccessPath ap0, NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC - | - pathNode(mid, midnode, state0, cc, sc, ap0, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), conf, - localCC) and - ap0 instanceof AccessPathNil - ) - or - jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - ap = mid.getAp() - or - additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, mid.getConfiguration()) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - pathIntoCallable(mid, node, state, _, cc, sc, _, _) and ap = mid.getAp() - or - pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone - or - pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() -} - -pragma[nomagic] -private predicate pathReadStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - tc = ap0.getHead() and - Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -pragma[nomagic] -private predicate pathStoreStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -private predicate pathOutOfCallable0( - PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa, - Configuration config -) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - apa = mid.getAp().getApprox() and - config = mid.getConfiguration() -} - -pragma[nomagic] -private predicate pathOutOfCallable1( - PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPathApprox apa, Configuration config -) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - pathOutOfCallable0(mid, pos, state, innercc, apa, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() +private predicate hasFlow(Node source, Node sink, Configuration config) { + exists(PathNode source0, PathNode sink0 | + hasFlowPath(source0, sink0, config) and + source0.getNode() = source and + sink0.getNode() = sink ) } -pragma[noinline] -private NodeEx getAnOutNodeFlow( - ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config -) { - result.asNode() = kind.getAnOutNode(call) and - Stage5::revFlow(result, _, apa, config) +private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) { + hasFlowPath(source, sink) and source.getConfiguration() = config } -/** - * Holds if data may flow from `mid` to `out`. The last step of this path - * is a return from a callable and is recorded by `cc`, if needed. - */ -pragma[noinline] -private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { - exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | - pathOutOfCallable1(mid, call, kind, state, cc, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} +private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) } -/** - * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. - */ -pragma[noinline] -private predicate pathIntoArg( - PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(ArgNodeEx arg, ArgumentPosition apos | - pathNode(mid, arg, state, cc, _, ap, config, _) and - arg.asNode().(ArgNode).argumentOf(call, apos) and - apa = ap.getApprox() and - parameterMatch(ppos, apos) - ) -} - -pragma[nomagic] -private predicate parameterCand( - DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::revFlow(p, _, apa, config) and - p.isParameterOf(callable, pos) - ) -} - -pragma[nomagic] -private predicate pathIntoCallable0( - PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, AccessPath ap, Configuration config -) { - exists(AccessPathApprox apa | - pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - callable = resolveCall(call, outercc) and - parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa), - pragma[only_bind_into](config)) - ) -} - -/** - * Holds if data may flow from `mid` to `p` through `call`. The contexts - * before and after entering the callable are `outercc` and `innercc`, - * respectively. - */ -pragma[nomagic] -private predicate pathIntoCallable( - PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, - SummaryCtx sc, DataFlowCall call, Configuration config -) { - exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | - pathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - ( - sc = TSummaryCtxSome(p, state, ap) - or - not exists(TSummaryCtxSome(p, state, ap)) and - sc = TSummaryCtxNone() and - // When the call contexts of source and sink needs to match then there's - // never any reason to enter a callable except to find a summary. See also - // the comment in `PathNodeMid::isAtSink`. - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) -} - -/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ -pragma[nomagic] -private predicate paramFlowsThrough( - ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, - AccessPathApprox apa, Configuration config -) { - exists(RetNodeEx ret | - pathNode(_, ret, state, cc, sc, ap, config, _) and - kind = ret.getKind() and - apa = ap.getApprox() and - parameterFlowThroughAllowed(sc.getParamNode(), kind) - ) -} - -pragma[nomagic] -private predicate pathThroughCallable0( - DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(CallContext innercc, SummaryCtx sc | - pathIntoCallable(mid, _, _, cc, innercc, sc, call, config) and - paramFlowsThrough(kind, state, innercc, sc, ap, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` through a callable to the node `out`. - * The context `cc` is restored to its value prior to entering the callable. - */ -pragma[noinline] -private predicate pathThroughCallable( - PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap -) { - exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config | - pathThroughCallable0(call, mid, kind, state, cc, ap, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -private module Subpaths { - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths01( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - exists(Configuration config | - pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and - pathIntoCallable(arg, par, _, _, innercc, sc, _, config) and - paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, - pragma[only_bind_into](apout), _, unbindConf(config)) and - not arg.isHidden() - ) - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `sout`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths02( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and - out.asNode() = kind.getAnOutNode(_) - } - - pragma[nomagic] - private Configuration getPathNodeConf(PathNodeImpl n) { result = n.getConfiguration() } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple. - */ - pragma[nomagic] - private predicate subpaths03( - PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, AccessPath apout - ) { - exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | - subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and - pathNode(ret, retnode, sout, innercc, sc, apout, unbindConf(getPathNodeConf(arg)), _) and - kind = retnode.getKind() - ) - } - - private PathNodeImpl localStepToHidden(PathNodeImpl n) { - n.getASuccessorImpl() = result and - result.isHidden() and - exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | - localFlowBigStep(n1, _, n2, _, _, _, _, _) or - store(n1, _, n2, _, _) or - readSet(n1, _, n2, _) - ) - } - - pragma[nomagic] - private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { - succ = pred.getANonHiddenSuccessor() and - succNode = succ.getNodeEx() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { - exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | - pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and - subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and - hasSuccessor(pragma[only_bind_into](arg), par, p) and - not ret.isHidden() and - pathNode(out0, o, sout, _, _, apout, _, _) - | - out = out0 or out = out0.projectToSink() - ) - } - - /** - * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. - */ - predicate retReach(PathNodeImpl n) { - exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) - or - exists(PathNodeImpl mid | - retReach(mid) and - n.getANonHiddenSuccessor() = mid and - not subpaths(_, mid, _, _) - ) - } -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -private predicate hasFlowPath( - PathNodeImpl flowsource, PathNodeImpl flowsink, Configuration configuration -) { - flowsource.isFlowSource() and - flowsource.getConfiguration() = configuration and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.isFlowSink() -} - -private predicate flowsTo( - PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink, - Configuration configuration -) { - flowsource.isSource() and - flowsource.getConfiguration() = configuration and - flowsource.getNodeEx().asNode() = source and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.getNodeEx().asNode() = sink -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -predicate flowsTo(Node source, Node sink, Configuration configuration) { - flowsTo(_, _, source, sink, configuration) -} - -private predicate finalStats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples -) { - fwd = true and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and - tuples = count(PathNodeImpl pn) - or - fwd = false and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and - tuples = count(PathNode pn) -} - -/** - * INTERNAL: Only for debugging. - * - * Calculates per-stage metrics for data flow. - */ -predicate stageStats( - int n, string stage, int nodes, int fields, int conscand, int states, int tuples, - Configuration config -) { - stage = "1 Fwd" and - n = 10 and - Stage1::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "1 Rev" and - n = 15 and - Stage1::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Fwd" and - n = 20 and - Stage2::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Rev" and - n = 25 and - Stage2::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Fwd" and - n = 30 and - Stage3::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Rev" and - n = 35 and - Stage3::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Fwd" and - n = 40 and - Stage4::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Rev" and - n = 45 and - Stage4::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Fwd" and - n = 50 and - Stage5::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Rev" and - n = 55 and - Stage5::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) - or - stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) -} - -private module FlowExploration { - private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - jumpStep(node1, node2, config) - or - additionalJumpStep(node1, node2, config) - or - additionalJumpStateStep(node1, _, node2, _, config) - or - // flow into callable - viableParamArgEx(_, node2, node1) - or - // flow out of a callable - viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) - | - c1 = node1.getEnclosingCallable() and - c2 = node2.getEnclosingCallable() and - c1 != c2 - ) - } - - private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSource(n) or config.isSource(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSrc(mid, config) and callableStep(mid, c, config) - ) - } - - private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSink(n) or config.isSink(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSink(mid, config) and callableStep(c, mid, config) - ) - } - - private newtype TCallableExt = - TCallable(DataFlowCallable c, Configuration config) { - interestingCallableSrc(c, config) or - interestingCallableSink(c, config) - } or - TCallableSrc() or - TCallableSink() - - private predicate callableExtSrc(TCallableSrc src) { any() } - - private predicate callableExtSink(TCallableSink sink) { any() } - - private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { - exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | - callableStep(c1, c2, config) and - ce1 = TCallable(c1, pragma[only_bind_into](config)) and - ce2 = TCallable(c2, pragma[only_bind_into](config)) - ) - or - exists(Node n, Configuration config | - ce1 = TCallableSrc() and - (config.isSource(n) or config.isSource(n, _)) and - ce2 = TCallable(getNodeEnclosingCallable(n), config) - ) - or - exists(Node n, Configuration config | - ce2 = TCallableSink() and - (config.isSink(n) or config.isSink(n, _)) and - ce1 = TCallable(getNodeEnclosingCallable(n), config) - ) - } - - private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { - callableExtStepFwd(ce2, ce1) - } - - private int distSrcExt(TCallableExt c) = - shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) - - private int distSinkExt(TCallableExt c) = - shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) - - private int distSrc(DataFlowCallable c, Configuration config) { - result = distSrcExt(TCallable(c, config)) - 1 - } - - private int distSink(DataFlowCallable c, Configuration config) { - result = distSinkExt(TCallable(c, config)) - 1 - } - - private newtype TPartialAccessPath = - TPartialNil(DataFlowType t) or - TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first - * element of the list and its length are tracked. If data flows from a source to - * a given node with a given `AccessPath`, this indicates the sequence of - * dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ - private class PartialAccessPath extends TPartialAccessPath { - abstract string toString(); - - TypedContent getHead() { this = TPartialCons(result, _) } - - int len() { - this = TPartialNil(_) and result = 0 - or - this = TPartialCons(_, result) - } - - DataFlowType getType() { - this = TPartialNil(result) - or - exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) - } - } - - private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { - override string toString() { - exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) - } - } - - private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { - override string toString() { - exists(TypedContent tc, int len | this = TPartialCons(tc, len) | - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private newtype TRevPartialAccessPath = - TRevPartialNil() or - TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `Content`s, but only the first - * element of the list and its length are tracked. - */ - private class RevPartialAccessPath extends TRevPartialAccessPath { - abstract string toString(); - - Content getHead() { this = TRevPartialCons(result, _) } - - int len() { - this = TRevPartialNil() and result = 0 - or - this = TRevPartialCons(_, result) - } - } - - private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { - override string toString() { result = "" } - } - - private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { - override string toString() { - exists(Content c, int len | this = TRevPartialCons(c, len) | - if len = 1 - then result = "[" + c.toString() + "]" - else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private predicate relevantState(FlowState state) { - sourceNode(_, state, _) or - sinkNode(_, state, _) or - additionalLocalStateStep(_, state, _, _, _) or - additionalLocalStateStep(_, _, _, state, _) or - additionalJumpStateStep(_, state, _, _, _) or - additionalJumpStateStep(_, _, _, state, _) - } - - private newtype TSummaryCtx1 = - TSummaryCtx1None() or - TSummaryCtx1Param(ParamNodeEx p) - - private newtype TSummaryCtx2 = - TSummaryCtx2None() or - TSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TSummaryCtx3 = - TSummaryCtx3None() or - TSummaryCtx3Some(PartialAccessPath ap) - - private newtype TRevSummaryCtx1 = - TRevSummaryCtx1None() or - TRevSummaryCtx1Some(ReturnPosition pos) - - private newtype TRevSummaryCtx2 = - TRevSummaryCtx2None() or - TRevSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TRevSummaryCtx3 = - TRevSummaryCtx3None() or - TRevSummaryCtx3Some(RevPartialAccessPath ap) - - private newtype TPartialPathNode = - TPartialPathNodeFwd( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = TPartialNil(node.getDataFlowType()) and - exists(config.explorationLimit()) - or - partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap, config) and - distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() - } or - TPartialPathNodeRev( - NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, - RevPartialAccessPath ap, Configuration config - ) { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() and - exists(config.explorationLimit()) - or - revPartialPathStep(_, node, state, sc1, sc2, sc3, ap, config) and - not clearsContentEx(node, ap.getHead()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead()) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() - } - - pragma[nomagic] - private predicate partialPathNodeMk0( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap, config) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - not clearsContentEx(node, ap.getHead().getContent()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead().getContent()) - ) and - if node.asNode() instanceof CastingNode - then compatibleTypes(node.getDataFlowType(), ap.getType()) - else any() - } - - /** - * A `Node` augmented with a call context, an access path, and a configuration. - */ - class PartialPathNode extends TPartialPathNode { - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() - } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - /** Gets the underlying `Node`. */ - final Node getNode() { this.getNodeEx().projectToNode() = result } - - FlowState getState() { none() } - - private NodeEx getNodeEx() { - result = this.(PartialPathNodeFwd).getNodeEx() or - result = this.(PartialPathNodeRev).getNodeEx() - } - - /** Gets the associated configuration. */ - Configuration getConfiguration() { none() } - - /** Gets a successor of this node, if any. */ - PartialPathNode getASuccessor() { none() } - - /** - * Gets the approximate distance to the nearest source measured in number - * of interprocedural steps. - */ - int getSourceDistance() { - result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - /** - * Gets the approximate distance to the nearest sink measured in number - * of interprocedural steps. - */ - int getSinkDistance() { - result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - private string ppAp() { - exists(string s | - s = this.(PartialPathNodeFwd).getAp().toString() or - s = this.(PartialPathNodeRev).getAp().toString() - | - if s = "" then result = "" else result = " " + s - ) - } - - private string ppCtx() { - result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" - } - - /** Holds if this is a source in a forward-flow path. */ - predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } - - /** Holds if this is a sink in a reverse-flow path. */ - predicate isRevSink() { this.(PartialPathNodeRev).isSink() } - } - - /** - * Provides the query predicates needed to include a graph in a path-problem query. - */ - module PartialPathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } - } - - private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { - NodeEx node; - FlowState state; - CallContext cc; - TSummaryCtx1 sc1; - TSummaryCtx2 sc2; - TSummaryCtx3 sc3; - PartialAccessPath ap; - Configuration config; - - PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - TSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TSummaryCtx3 getSummaryCtx3() { result = sc3 } - - PartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeFwd getASuccessor() { - partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp(), - result.getConfiguration()) - } - - predicate isSource() { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap instanceof TPartialNil - } - } - - private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { - NodeEx node; - FlowState state; - TRevSummaryCtx1 sc1; - TRevSummaryCtx2 sc2; - TRevSummaryCtx3 sc3; - RevPartialAccessPath ap; - Configuration config; - - PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } - - RevPartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeRev getASuccessor() { - revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), - this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp(), this.getConfiguration()) - } - - predicate isSink() { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() - } - } - - private predicate partialPathStep( - PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, - TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and - ( - localFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - ) - or - jumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - partialPathStoreStep(mid, _, _, node, ap) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(PartialAccessPath ap0, TypedContent tc | - partialPathReadStep(mid, ap0, tc, node, cc, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsFwd(ap, tc, ap0, config) - ) - or - partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap, config) - or - partialPathOutOfCallable(mid, node, state, cc, ap, config) and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() - or - partialPathThroughCallable(mid, node, state, cc, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - bindingset[result, i] - private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } - - pragma[inline] - private predicate partialPathStoreStep( - PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, - PartialAccessPath ap2 - ) { - exists(NodeEx midNode, DataFlowType contentType | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - store(midNode, tc, node, contentType, mid.getConfiguration()) and - ap2.getHead() = tc and - ap2.len() = unbindInt(ap1.len() + 1) and - compatibleTypes(ap1.getType(), contentType) - ) - } - - pragma[nomagic] - private predicate apConsFwd( - PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeFwd mid | - partialPathStoreStep(mid, ap1, tc, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate partialPathReadStep( - PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc, - Configuration config - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and - ap.getHead() = tc and - pragma[only_bind_into](config) = mid.getConfiguration() and - cc = mid.getCallContext() - ) - } - - private predicate partialPathOutOfCallable0( - PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, - PartialAccessPath ap, Configuration config - ) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - ap = mid.getAp() and - config = mid.getConfiguration() - } - - pragma[nomagic] - private predicate partialPathOutOfCallable1( - PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - partialPathOutOfCallable0(mid, pos, state, innercc, ap, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) - } - - private predicate partialPathOutOfCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(ReturnKindExt kind, DataFlowCall call | - partialPathOutOfCallable1(mid, call, kind, state, cc, ap, config) - | - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[noinline] - private predicate partialPathIntoArg( - PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ArgNode arg, ArgumentPosition apos | - arg = mid.getNodeEx().asNode() and - state = mid.getState() and - cc = mid.getCallContext() and - arg.argumentOf(call, apos) and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate partialPathIntoCallable0( - PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - partialPathIntoArg(mid, pos, state, outercc, call, ap, config) and - callable = resolveCall(call, outercc) - } - - private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, - CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ParameterPosition pos, DataFlowCallable callable | - partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - sc1 = TSummaryCtx1Param(p) and - sc2 = TSummaryCtx2Some(state) and - sc3 = TSummaryCtx3Some(ap) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) - } - - pragma[nomagic] - private predicate paramFlowsThroughInPartialPath( - ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeFwd mid, RetNodeEx ret | - mid.getNodeEx() = ret and - kind = ret.getKind() and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() and - ap = mid.getAp() - ) - } - - pragma[noinline] - private predicate partialPathThroughCallable0( - DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | - partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _, config) and - paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap, config) - ) - } - - private predicate partialPathThroughCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ReturnKindExt kind | - partialPathThroughCallable0(call, mid, kind, state, cc, ap, config) and - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[nomagic] - private predicate revPartialPathStep( - PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, - TRevSummaryCtx3 sc3, RevPartialAccessPath ap, Configuration config - ) { - localFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - jumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - revPartialPathReadStep(mid, _, _, node, ap) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(RevPartialAccessPath ap0, Content c | - revPartialPathStoreStep(mid, ap0, c, node, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsRev(ap, c, ap0, config) - ) - or - exists(ParamNodeEx p | - mid.getNodeEx() = p and - viableParamArgEx(_, p, node) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - or - exists(ReturnPosition pos | - revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap, config) and - pos = getReturnPosition(node.asNode()) - ) - or - revPartialPathThroughCallable(mid, node, state, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - pragma[inline] - private predicate revPartialPathReadStep( - PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, - RevPartialAccessPath ap2 - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - read(node, c, midNode, mid.getConfiguration()) and - ap2.getHead() = c and - ap2.len() = unbindInt(ap1.len() + 1) - ) - } - - pragma[nomagic] - private predicate apConsRev( - RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeRev mid | - revPartialPathReadStep(mid, ap1, c, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathStoreStep( - PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config - ) { - exists(NodeEx midNode, TypedContent tc | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - store(node, tc, midNode, _, config) and - ap.getHead() = c and - config = mid.getConfiguration() and - tc.getContent() = c - ) - } - - pragma[nomagic] - private predicate revPartialPathIntoReturn( - PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, - TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap, - Configuration config - ) { - exists(NodeEx out | - mid.getNodeEx() = out and - mid.getState() = state and - viableReturnPosOutEx(call, pos, out) and - sc1 = TRevSummaryCtx1Some(pos) and - sc2 = TRevSummaryCtx2Some(state) and - sc3 = TRevSummaryCtx3Some(ap) and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathFlowsThrough( - ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, - TRevSummaryCtx3Some sc3, RevPartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | - mid.getNodeEx() = p and - mid.getState() = state and - p.getPosition() = ppos and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable0( - DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, - RevPartialAccessPath ap, Configuration config - ) { - exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | - revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _, config) and - revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap, config) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ArgumentPosition pos | - revPartialPathThroughCallable0(call, mid, pos, state, ap, config) and - node.asNode().(ArgNode).argumentOf(call, pos) - ) - } -} - -import FlowExploration - -private predicate partialFlow( - PartialPathNode source, PartialPathNode node, Configuration configuration -) { - source.getConfiguration() = configuration and - source.isFwdSource() and - node = source.getASuccessor+() -} - -private predicate revPartialFlow( - PartialPathNode node, PartialPathNode sink, Configuration configuration -) { - sink.getConfiguration() = configuration and - sink.isRevSink() and - node.getASuccessor+() = sink -} +predicate flowsTo = hasFlow/3; diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll index 5d3becc8078..f8c56d41d8d 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll @@ -3,15 +3,18 @@ private import DataFlowImplSpecific::Public import Cached module DataFlowImplCommonPublic { - /** A state value to track during data flow. */ - class FlowState = string; + /** Provides `FlowState = string`. */ + module FlowStateString { + /** A state value to track during data flow. */ + class FlowState = string; - /** - * The default state, which is used when the state is unspecified for a source - * or a sink. - */ - class FlowStateEmpty extends FlowState { - FlowStateEmpty() { this = "" } + /** + * The default state, which is used when the state is unspecified for a source + * or a sink. + */ + class FlowStateEmpty extends FlowState { + FlowStateEmpty() { this = "" } + } } private newtype TFlowFeature = diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll index 1b969756b09..e6bdc74cceb 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll @@ -1,16 +1,16 @@ /** - * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from - * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed - * through the `Configuration` class. This file exists in several identical - * copies, allowing queries to use multiple `Configuration` classes that depend - * on each other without introducing mutual recursion among those configurations. + * DEPRECATED: Use `Make` and `MakeWithState` instead. + * + * Provides a `Configuration` class backwards-compatible interface to the data + * flow library. */ private import DataFlowImplCommon private import DataFlowImplSpecific::Private import DataFlowImplSpecific::Public +private import DataFlowImpl import DataFlowImplCommonPublic +import FlowStateString /** * A configuration of interprocedural data flow analysis. This defines @@ -144,6 +144,10 @@ abstract class Configuration extends string { * - `FeatureEqualSourceSinkCallContext`: * Implies both of the above and additionally ensures that the entire flow * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. */ FlowFeature getAFeature() { none() } @@ -156,7 +160,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from `source` to `sink` for this configuration. */ - predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } + predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) } /** * Holds if data may flow from `source` to `sink` for this configuration. @@ -169,9 +173,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from some source to `sink` for this configuration. */ - predicate hasFlowTo(Node sink) { - sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode() - } + predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) } /** * Holds if data may flow from some source to `sink` for this configuration. @@ -179,10 +181,12 @@ abstract class Configuration extends string { predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } /** + * DEPRECATED: Use `FlowExploration` instead. + * * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` * measured in approximate number of interprocedural steps. */ - int explorationLimit() { none() } + deprecated int explorationLimit() { none() } /** * Holds if hidden nodes should be included in the data flow graph. @@ -191,49 +195,6 @@ abstract class Configuration extends string { * is not visualized (for example in a `path-problem` query). */ predicate includeHiddenNodes() { none() } - - /** - * Holds if there is a partial data flow path from `source` to `node`. The - * approximate distance between `node` and the closest source is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards sink definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sources is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - */ - final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { - partialFlow(source, node, this) and - dist = node.getSourceDistance() - } - - /** - * Holds if there is a partial data flow path from `node` to `sink`. The - * approximate distance between `node` and the closest sink is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards source definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sinks is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - * - * Note that reverse flow has slightly lower precision than the corresponding - * forward flow, as reverse flow disregards type pruning among other features. - */ - final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { - revPartialFlow(node, sink, this) and - dist = node.getSinkDistance() - } } /** @@ -263,90 +224,6 @@ abstract private class ConfigurationRecursionPrevention extends Configuration { } } -private newtype TNodeEx = - TNodeNormal(Node n) or - TNodeImplicitRead(Node n, boolean hasRead) { - any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true] - } - -private class NodeEx extends TNodeEx { - string toString() { - result = this.asNode().toString() - or - exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") - } - - Node asNode() { this = TNodeNormal(result) } - - predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } - - Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } - - pragma[nomagic] - private DataFlowCallable getEnclosingCallable0() { - nodeEnclosingCallable(this.projectToNode(), result) - } - - pragma[inline] - DataFlowCallable getEnclosingCallable() { - pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) - } - - pragma[nomagic] - private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } - - pragma[inline] - DataFlowType getDataFlowType() { - pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) - } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -private class ArgNodeEx extends NodeEx { - ArgNodeEx() { this.asNode() instanceof ArgNode } -} - -private class ParamNodeEx extends NodeEx { - ParamNodeEx() { this.asNode() instanceof ParamNode } - - predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { - this.asNode().(ParamNode).isParameterOf(c, pos) - } - - ParameterPosition getPosition() { this.isParameterOf(_, result) } -} - -private class RetNodeEx extends NodeEx { - RetNodeEx() { this.asNode() instanceof ReturnNodeExt } - - ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } - - ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } -} - -private predicate inBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierIn(n) - | - config.isSource(n) or config.isSource(n, _) - ) -} - -private predicate outBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierOut(n) - | - config.isSink(n) or config.isSink(n, _) - ) -} - /** A bridge class to access the deprecated `isBarrierGuard`. */ private class BarrierGuardGuardedNodeBridge extends Unit { abstract predicate guardedNode(Node n, Configuration config); @@ -370,3019 +247,94 @@ private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { } } -pragma[nomagic] -private predicate fullBarrier(NodeEx node, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n) +private FlowState relevantState(Configuration config) { + config.isSource(_, result) or + config.isSink(_, result) or + config.isBarrier(_, result) or + config.isAdditionalFlowStep(_, result, _, _) or + config.isAdditionalFlowStep(_, _, _, result) +} + +private newtype TConfigState = + TMkConfigState(Configuration config, FlowState state) { + state = relevantState(config) or state instanceof FlowStateEmpty + } + +private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) } + +private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) } + +private predicate singleConfiguration() { 1 = strictcount(Configuration c) } + +private module Config implements FullStateConfigSig { + class FlowState = TConfigState; + + predicate isSource(Node source, FlowState state) { + getConfig(state).isSource(source, getState(state)) or - config.isBarrierIn(n) and - not config.isSource(n) and - not config.isSource(n, _) + getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty + } + + predicate isSink(Node sink, FlowState state) { + getConfig(state).isSink(sink, getState(state)) or - config.isBarrierOut(n) and - not config.isSink(n) and - not config.isSink(n, _) + getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty + } + + predicate isBarrier(Node node) { none() } + + predicate isBarrier(Node node, FlowState state) { + getConfig(state).isBarrier(node, getState(state)) or + getConfig(state).isBarrier(node) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state)) + } + + predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) } + + predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) } + + predicate isAdditionalFlowStep(Node node1, Node node2) { + singleConfiguration() and + any(Configuration config).isAdditionalFlowStep(node1, node2) + } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and + getConfig(state2) = getConfig(state1) or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config) - ) -} - -pragma[nomagic] -private predicate stateBarrier(NodeEx node, FlowState state, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n, state) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config) - ) -} - -pragma[nomagic] -private predicate sourceNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSource(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSource(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -pragma[nomagic] -private predicate sinkNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSink(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSink(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -/** Provides the relevant barriers for a step from `node1` to `node2`. */ -pragma[inline] -private predicate stepFilter(NodeEx node1, NodeEx node2, Configuration config) { - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) -} - -/** - * Holds if data can flow in one local step from `node1` to `node2`. - */ -private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.asNode() = n and - node2.isImplicitReadNode(n, false) and - not fullBarrier(node1, config) - ) -} - -/** - * Holds if the additional step from `node1` to `node2` does not jump between callables. - */ -private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.isImplicitReadNode(n, true) and - node2.asNode() = n and - not fullBarrier(node2, config) - ) -} - -private predicate additionalLocalStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) - ) -} - -/** - * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. - */ -private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if the additional step from `node1` to `node2` jumps between callables. - */ -private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate additionalJumpStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -pragma[nomagic] -private predicate readSet(NodeEx node1, ContentSet c, NodeEx node2, Configuration config) { - readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and - stepFilter(node1, node2, config) - or - exists(Node n | - node2.isImplicitReadNode(n, true) and - node1.isImplicitReadNode(n, _) and - config.allowImplicitRead(n, c) - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(ContentSet cs | - readSet(node1, cs, node2, config) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate clearsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - clearsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate expectsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - expectsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -pragma[nomagic] -private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } - -pragma[nomagic] -private predicate hasReadStep(Content c, Configuration config) { read(_, c, _, config) } - -pragma[nomagic] -private predicate store( - NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config -) { - store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), - contentType) and - hasReadStep(tc.getContent(), config) and - stepFilter(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { - viableReturnPosOut(call, pos, out.asNode()) -} - -pragma[nomagic] -private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { - viableParamArg(call, p.asNode(), arg.asNode()) -} - -/** - * Holds if field flow should be used for the given configuration. - */ -private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } - -private predicate hasSourceCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSourceCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate hasSinkCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSinkCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if flow from `p` to a return node of kind `kind` is allowed. - * - * We don't expect a parameter to return stored in itself, unless - * explicitly allowed - */ -bindingset[p, kind] -private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { - exists(ParameterPosition pos | p.isParameterOf(_, pos) | - not kind.(ParamUpdateReturnKind).getPosition() = pos - or - allowParameterReturnInSelfCached(p.asNode()) - ) -} - -private module Stage1 implements StageSig { - class Ap extends int { - // workaround for bad functionality-induced joins (happens when using `Unit`) - pragma[nomagic] - Ap() { this in [0 .. 1] and this < 1 } - } - - private class Cc = boolean; - - /* Begin: Stage 1 logic. */ - /** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `cc` records whether the node is reached through an - * argument in a call. - */ - private predicate fwdFlow(NodeEx node, Cc cc, Configuration config) { - sourceNode(node, _, config) and - if hasSourceCallCtx(config) then cc = true else cc = false - or - exists(NodeEx mid | fwdFlow(mid, cc, config) | - localFlowStep(mid, node, config) or - additionalLocalFlowStep(mid, node, config) or - additionalLocalStateStep(mid, _, node, _, config) - ) - or - exists(NodeEx mid | fwdFlow(mid, _, config) and cc = false | - jumpStep(mid, node, config) or - additionalJumpStep(mid, node, config) or - additionalJumpStateStep(mid, _, node, _, config) - ) - or - // store - exists(NodeEx mid | - useFieldFlow(config) and - fwdFlow(mid, cc, config) and - store(mid, _, node, _, config) - ) - or - // read - exists(ContentSet c | - fwdFlowReadSet(c, node, cc, config) and - fwdFlowConsCandSet(c, _, config) - ) - or - // flow into a callable - fwdFlowIn(_, _, _, node, config) and - cc = true - or - // flow out of a callable - fwdFlowOut(_, node, false, config) and - cc = false - or - // flow through a callable - exists(DataFlowCall call | - fwdFlowOutFromArg(call, node, config) and - fwdFlowIsEntered(call, cc, config) - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowIn( - DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p, Configuration config - ) { - // call context cannot help reduce virtual dispatch - fwdFlow(arg, cc, config) and - viableParamArgEx(call, p, arg) and - not fullBarrier(p, config) and - ( - cc = false - or - cc = true and - not reducedViableImplInCallContext(call, _, _) - ) - or - // call context may help reduce virtual dispatch - exists(DataFlowCallable target | - fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target, config) and - target = viableImplInSomeFwdFlowCallContextExt(call, config) and - cc = true - ) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - fwdFlowIn(call, _, cc, _, config) - } - - pragma[nomagic] - private predicate fwdFlowInReducedViableImplInSomeCallContext( - DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target, Configuration config - ) { - fwdFlow(arg, true, config) and - viableParamArgEx(call, p, arg) and - reducedViableImplInCallContext(call, _, _) and - target = p.getEnclosingCallable() and - not fullBarrier(p, config) - } - - /** - * Gets a viable dispatch target of `call` in the context `ctx`. This is - * restricted to those `call`s for which a context might make a difference, - * and to `ctx`s that are reachable in `fwdFlow`. - */ - pragma[nomagic] - private DataFlowCallable viableImplInSomeFwdFlowCallContextExt( - DataFlowCall call, Configuration config - ) { - exists(DataFlowCall ctx | - fwdFlowIsEntered(ctx, _, config) and - result = viableImplInCallContextExt(call, ctx) - ) - } - - private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) } - - pragma[nomagic] - private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc, Configuration config) { - exists(NodeEx mid | - fwdFlow(mid, cc, config) and - readSet(mid, c, node, config) - ) - } - - /** - * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - fwdFlow(mid, _, config) and - store(mid, tc, node, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `cs` may be interpreted in a read as the target of some store - * into `c`, in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCandSet(ContentSet cs, Content c, Configuration config) { - fwdFlowConsCand(c, config) and - c = cs.getAReadContent() - } - - pragma[nomagic] - private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { - exists(RetNodeEx ret | - fwdFlow(ret, cc, config) and - ret.getReturnPosition() = pos - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) { - exists(ReturnPosition pos | - fwdFlowReturnPosition(pos, cc, config) and - viableReturnPosOutEx(call, pos, out) and - not fullBarrier(out, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) { - fwdFlowOut(call, out, true, config) - } - - private predicate stateStepFwd(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1 | - additionalLocalStateStep(node1, state1, _, state2, config) or - additionalJumpStateStep(node1, state1, _, state2, config) - | - fwdFlow(node1, config) - ) - } - - private predicate fwdFlowState(FlowState state, Configuration config) { - sourceNode(_, state, config) - or - exists(FlowState state0 | - fwdFlowState(state0, config) and - stateStepFwd(state0, state, config) - ) - } - - /** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink. - */ - pragma[nomagic] - private predicate revFlow(NodeEx node, boolean toReturn, Configuration config) { - revFlow0(node, toReturn, config) and - fwdFlow(node, config) - } - - pragma[nomagic] - private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) { - exists(FlowState state | - fwdFlow(node, pragma[only_bind_into](config)) and - sinkNode(node, state, config) and - fwdFlowState(state, pragma[only_bind_into](config)) and - if hasSinkCallCtx(config) then toReturn = true else toReturn = false - ) - or - exists(NodeEx mid | revFlow(mid, toReturn, config) | - localFlowStep(node, mid, config) or - additionalLocalFlowStep(node, mid, config) or - additionalLocalStateStep(node, _, mid, _, config) - ) - or - exists(NodeEx mid | revFlow(mid, _, config) and toReturn = false | - jumpStep(node, mid, config) or - additionalJumpStep(node, mid, config) or - additionalJumpStateStep(node, _, mid, _, config) - ) - or - // store - exists(Content c | - revFlowStore(c, node, toReturn, config) and - revFlowConsCand(c, config) - ) - or - // read - exists(NodeEx mid, ContentSet c | - readSet(node, c, mid, config) and - fwdFlowConsCandSet(c, _, pragma[only_bind_into](config)) and - revFlow(mid, toReturn, pragma[only_bind_into](config)) - ) - or - // flow into a callable - revFlowIn(_, node, false, config) and - toReturn = false - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(pos, config) and - node.(RetNodeEx).getReturnPosition() = pos and - toReturn = true - ) - or - // flow through a callable - exists(DataFlowCall call | - revFlowInToReturn(call, node, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - /** - * Holds if `c` is the target of a read in the flow covered by `revFlow`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, ContentSet cs | - fwdFlow(node, pragma[only_bind_into](config)) and - readSet(node, cs, mid, config) and - fwdFlowConsCandSet(cs, c, pragma[only_bind_into](config)) and - revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) { - exists(NodeEx mid, TypedContent tc | - revFlow(mid, toReturn, pragma[only_bind_into](config)) and - fwdFlowConsCand(c, pragma[only_bind_into](config)) and - store(node, tc, mid, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `c` is the target of both a read and a store in the flow covered - * by `revFlow`. - */ - pragma[nomagic] - additional predicate revFlowIsReadAndStored(Content c, Configuration conf) { - revFlowConsCand(c, conf) and - revFlowStore(c, _, _, conf) - } - - pragma[nomagic] - additional predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config - ) { - fwdFlowReturnPosition(pos, _, config) and - viableReturnPosOutEx(call, pos, out) - } - - pragma[nomagic] - private predicate revFlowOut(ReturnPosition pos, Configuration config) { - exists(NodeEx out | - revFlow(out, _, config) and - viableReturnPosOutNodeCandFwd1(_, pos, out, config) - ) - } - - pragma[nomagic] - additional predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config - ) { - fwdFlowIn(call, arg, _, p, config) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate revFlowIn( - DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config - ) { - exists(ParamNodeEx p | - revFlow(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) { - revFlowIn(call, arg, true, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(NodeEx out | - revFlow(out, toReturn, config) and - fwdFlowOutFromArg(call, out, config) - ) - } - - private predicate stateStepRev(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - additionalLocalStateStep(node1, state1, node2, state2, config) or - additionalJumpStateStep(node1, state1, node2, state2, config) - | - revFlow(node1, _, pragma[only_bind_into](config)) and - revFlow(node2, _, pragma[only_bind_into](config)) and - fwdFlowState(state1, pragma[only_bind_into](config)) and - fwdFlowState(state2, pragma[only_bind_into](config)) - ) - } - - additional predicate revFlowState(FlowState state, Configuration config) { - exists(NodeEx node | - sinkNode(node, state, config) and - revFlow(node, _, pragma[only_bind_into](config)) and - fwdFlowState(state, pragma[only_bind_into](config)) - ) - or - exists(FlowState state0 | - revFlowState(state0, config) and - stateStepRev(state, state0, config) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Content c | - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - revFlow(node2, pragma[only_bind_into](config)) and - store(node1, tc, node2, contentType, config) and - c = tc.getContent() and - exists(ap1) - ) - } - - pragma[nomagic] - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) { - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - read(n1, c, n2, pragma[only_bind_into](config)) and - revFlow(n2, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, config) and - exists(ap) - } - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, _, pragma[only_bind_into](config)) and - exists(state) and - exists(ap) - } - - private predicate throughFlowNodeCand(NodeEx node, Configuration config) { - revFlow(node, true, config) and - fwdFlow(node, true, config) and - not inBarrier(node, config) and - not outBarrier(node, config) - } - - /** Holds if flow may return from `callable`. */ - pragma[nomagic] - private predicate returnFlowCallableNodeCand( - DataFlowCallable callable, ReturnKindExt kind, Configuration config - ) { - exists(RetNodeEx ret | - throughFlowNodeCand(ret, config) and - callable = ret.getEnclosingCallable() and - kind = ret.getKind() - ) - } - - /** - * Holds if flow may enter through `p` and reach a return node making `p` a - * candidate for the origin of a summary. - */ - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(DataFlowCallable c, ReturnKindExt kind | - throughFlowNodeCand(p, config) and - returnFlowCallableNodeCand(c, kind, config) and - p.getEnclosingCallable() = c and - exists(ap) and - parameterFlowThroughAllowed(p, kind) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - throughFlowNodeCand(ret, config) and - kind = ret.getKind() and - exists(argAp) and - exists(ap) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, boolean toReturn | - revFlow(arg, toReturn, config) and - revFlowInToReturn(call, arg, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, config)) and - fields = count(Content f0 | fwdFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | fwdFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, config)) and - fields = count(Content f0 | revFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | revFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | revFlow(n, b, config)) - } - /* End: Stage 1 logic. */ -} - -pragma[noinline] -private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - localFlowStep(node1, node2, config) -} - -pragma[noinline] -private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - additionalLocalFlowStep(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCand1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config -) { - Stage1::revFlow(out, config) and - Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, Configuration config -) { - exists(ReturnPosition pos | - viableReturnPosOutNodeCand1(call, pos, out, config) and - pos = ret.getReturnPosition() and - kind = pos.getKind() and - Stage1::revFlow(ret, config) and - not outBarrier(ret, config) and - not inBarrier(out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCand1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config -) { - Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and - Stage1::revFlow(arg, config) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config -) { - viableParamArgNodeCand1(call, p, arg, config) and - Stage1::revFlow(p, config) and - not outBarrier(arg, config) and - not inBarrier(p, config) -} - -/** - * Gets the amount of forward branching on the origin of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int branch(NodeEx n1, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n1, _, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) - ) -} - -/** - * Gets the amount of backward branching on the target of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int join(NodeEx n2, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n, _, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) - ) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. The - * `allowsFieldFlow` flag indicates whether the branching is within the limit - * specified by the configuration. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, ret, kind, out, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(ret, pragma[only_bind_into](config)) and - j = join(out, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. The `allowsFieldFlow` flag indicates whether - * the branching is within the limit specified by the configuration. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config -) { - flowIntoCallNodeCand1(call, arg, p, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(arg, pragma[only_bind_into](config)) and - j = join(p, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -private signature module StageSig { - class Ap; - - predicate revFlow(NodeEx node, Configuration config); - - predicate revFlowAp(NodeEx node, Ap ap, Configuration config); - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config); - - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config); - - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config); - - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ); - - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ); - - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config); -} - -private module MkStage { - class ApApprox = PrevStage::Ap; - - signature module StageParam { - class Ap; - - class ApNil extends Ap; - - bindingset[result, ap] - ApApprox getApprox(Ap ap); - - ApNil getApNil(NodeEx node); - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail); - - /** - * An approximation of `Content` that corresponds to the precision level of - * `Ap`, such that the mappings from both `Ap` and `Content` to this type - * are functional. - */ - class ApHeadContent; - - ApHeadContent getHeadContent(Ap ap); - - ApHeadContent projectToHeadContent(Content c); - - class ApOption; - - ApOption apNone(); - - ApOption apSome(Ap ap); - - class Cc; - - class CcCall extends Cc; - - // TODO: member predicate on CcCall - predicate matchesCall(CcCall cc, DataFlowCall call); - - class CcNoCall extends Cc; - - Cc ccNone(); - - CcCall ccSomeCall(); - - class LocalCc; - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc); - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ); - - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config - ); - - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config - ); - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config); - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType); - } - - module Stage implements StageSig { - import Param - - /* Begin: Stage logic. */ - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - private predicate revFlowApAlias(NodeEx node, ApApprox apa, Configuration config) { - PrevStage::revFlowAp(node, apa, config) - } - - pragma[nomagic] - private predicate flowIntoCallApa( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa, - Configuration config - ) { - flowIntoCall(call, arg, p, allowsFieldFlow, config) and - PrevStage::revFlowAp(p, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(arg, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowOutOfCallApa( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - ApApprox apa, Configuration config - ) { - flowOutOfCall(call, ret, kind, out, allowsFieldFlow, config) and - PrevStage::revFlowAp(out, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(ret, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowThroughOutOfCall( - DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, - ApApprox argApa, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa, pragma[only_bind_into](config)) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) and - matchesCall(ccc, call) - ) - } - - /** - * Holds if `node` is reachable with access path `ap` from a source in the - * configuration `config`. - * - * The call context `cc` records whether the node is reached through an - * argument in a call, and if so, `summaryCtx` and `argAp` record the - * corresponding parameter position and access path of that argument, respectively. - */ - pragma[nomagic] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa, config) and - PrevStage::revFlow(node, state, apa, config) and - filter(node, state, ap, config) - } - - pragma[inline] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, cc, summaryCtx, argAp, ap, _, config) - } - - pragma[nomagic] - private predicate fwdFlow0( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - sourceNode(node, state, config) and - (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and - argAp = apNone() and - summaryCtx = TParamNodeNone() and - ap = getApNil(node) and - apa = getApprox(ap) - or - exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | - fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0, config) and - localCc = getLocalCc(mid, cc) - | - localStep(mid, state0, node, state, true, _, config, localCc) and - ap = ap0 and - apa = apa0 - or - localStep(mid, state0, node, state, false, ap, config, localCc) and - ap0 instanceof ApNil and - apa = getApprox(ap) - ) - or - exists(NodeEx mid | - fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa, pragma[only_bind_into](config)) and - jumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(mid, state, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(mid, state0, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStateStep(mid, state0, node, state, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - // store - exists(TypedContent tc, Ap ap0 | - fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp, config) and - ap = apCons(tc, ap0) and - apa = getApprox(ap) - ) - or - // read - exists(Ap ap0, Content c | - fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp, config) and - fwdFlowConsCand(ap0, c, ap, config) and - apa = getApprox(ap) - ) - or - // flow into a callable - fwdFlowIn(_, node, state, _, cc, _, _, ap, apa, config) and - if PrevStage::parameterMayFlowThrough(node, apa, config) - then ( - summaryCtx = TParamNodeSome(node.asNode()) and - argAp = apSome(ap) - ) else ( - summaryCtx = TParamNodeNone() and argAp = apNone() - ) - or - // flow out of a callable - exists( - DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, - DataFlowCallable inner - | - fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa, config) and - flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa, config) and - inner = ret.getEnclosingCallable() and - cc = getCallContextReturn(inner, call, innercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - or - // flow through a callable - exists( - DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, ApApprox innerArgApa - | - fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowStore( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(DataFlowType contentType, ApApprox apa1 | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1, config) and - PrevStage::storeStepCand(node1, apa1, tc, node2, contentType, config) and - typecheckStore(ap1, contentType) - ) - } - - /** - * Holds if forward flow with access path `tail` reaches a store of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(TypedContent tc | - fwdFlowStore(_, tail, tc, _, _, _, _, _, config) and - tc.getContent() = c and - cons = apCons(tc, tail) - ) - } - - pragma[nomagic] - private predicate readStepCand( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - PrevStage::readStepCand(node1, c, node2, config) and - apc = projectToHeadContent(c) - } - - bindingset[node1, apc] - pragma[inline_late] - private predicate readStepCand0( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - readStepCand(node1, apc, c, node2, config) - } - - pragma[nomagic] - private predicate fwdFlowRead( - Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(ApHeadContent apc | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap, config) and - apc = getHeadContent(ap) and - readStepCand0(node1, apc, c, node2, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowIn( - DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, - ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa, Configuration config - ) { - exists(ArgNodeEx arg, boolean allowsFieldFlow | - fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa, config) and - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowRetFromArg( - RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, ApApprox argApa, - Ap ap, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - fwdFlow(pragma[only_bind_into](ret), state, ccc, - TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), - pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa), - pragma[only_bind_into](config)) and - kind = ret.getKind() and - parameterFlowThroughAllowed(summaryCtx, kind) and - argApa = getApprox(argAp) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) - ) - } - - pragma[inline] - private predicate fwdFlowThrough0( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, - Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa, config) and - fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp, config) - } - - pragma[nomagic] - private predicate fwdFlowThrough( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa, - config) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` - * and data might flow through the target callable and back out at `call`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered( - DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, - ParamNodeEx p, Ap ap, Configuration config - ) { - exists(ApApprox apa | - fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(p, apa, config) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate storeStepFwd( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config - ) { - fwdFlowStore(node1, ap1, tc, node2, _, _, _, _, config) and - ap2 = apCons(tc, ap1) and - fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _, config) - } - - private predicate readStepFwd( - NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config - ) { - fwdFlowRead(ap1, c, n1, n2, _, _, _, _, config) and - fwdFlowConsCand(ap1, c, ap2, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough0( - DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, - ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, - innerArgApa, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough( - RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, - Ap ap, Configuration config - ) { - exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | - returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa, config) and - pos = ret.getReturnPosition() and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowThroughIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap, - Configuration config - ) { - exists(ApApprox argApa | - flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), - allowsFieldFlow, argApa, pragma[only_bind_into](config)) and - fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa, - pragma[only_bind_into](config)) and - returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap, - pragma[only_bind_into](config)) and - if allowsFieldFlow = false then argAp instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowIntoCallAp( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap, - Configuration config - ) { - exists(ApApprox apa | - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - fwdFlow(arg, _, _, _, _, ap, apa, config) - ) - } - - pragma[nomagic] - private predicate flowOutOfCallAp( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, - Ap ap, Configuration config - ) { - exists(ApApprox apa | - flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa, config) and - fwdFlow(ret, _, _, _, _, ap, apa, config) and - pos = ret.getReturnPosition() - ) - } - - /** - * Holds if `node` with access path `ap` is part of a path from a source to a - * sink in the configuration `config`. - * - * The parameter `returnCtx` records whether (and how) the node must be returned - * from the enclosing callable in order to reach a sink, and if so, `returnAp` - * records the access path of the returned value. - */ - pragma[nomagic] - additional predicate revFlow( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - revFlow0(node, state, returnCtx, returnAp, ap, config) and - fwdFlow(node, state, _, _, _, ap, config) - } - - pragma[nomagic] - private predicate revFlow0( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, _, _, _, ap, config) and - sinkNode(node, state, config) and - ( - if hasSinkCallCtx(config) - then returnCtx = TReturnCtxNoFlowThrough() - else returnCtx = TReturnCtxNone() - ) and - returnAp = apNone() and - ap instanceof ApNil - or - exists(NodeEx mid, FlowState state0 | - localStep(node, state, mid, state0, true, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, ap, config) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap, pragma[only_bind_into](config)) and - localStep(node, pragma[only_bind_into](state), mid, state0, false, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, nil, pragma[only_bind_into](config)) and - ap instanceof ApNil - ) - or - exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, state, _, _, ap, config) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStep(node, mid, config) and - revFlow(pragma[only_bind_into](mid), state, _, _, nil, pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStateStep(node, state, mid, state0, config) and - revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil, - pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - // store - exists(Ap ap0, Content c | - revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp, config) and - revFlowConsCand(ap0, c, ap, config) - ) - or - // read - exists(NodeEx mid, Ap ap0 | - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - readStepFwd(node, ap, _, mid, ap0, config) - ) - or - // flow into a callable - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, state, TReturnCtxNone(), returnAp, ap, config) and - flowIntoCallAp(_, node, p, allowsFieldFlow, ap, config) and - (if allowsFieldFlow = false then ap instanceof ApNil else any()) and - returnCtx = TReturnCtxNone() - ) - or - // flow through a callable - exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, node, p, _, ap, innerReturnAp, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(_, node, pos, state, _, _, ap, config) and - if returnFlowsThrough(node, pos, state, _, _, _, ap, config) - then ( - returnCtx = TReturnCtxMaybeFlowThrough(pos) and - returnAp = apSome(ap) - ) else ( - returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() - ) - ) - } - - pragma[nomagic] - private predicate revFlowStore( - Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, - ReturnCtx returnCtx, ApOption returnAp, Configuration config - ) { - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - storeStepFwd(node, ap, tc, mid, ap0, config) and - tc.getContent() = c - } - - /** - * Holds if reverse flow with access path `tail` reaches a read of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(NodeEx mid, Ap tail0 | - revFlow(mid, _, _, _, tail, config) and - tail = pragma[only_bind_into](tail0) and - readStepFwd(_, cons, c, mid, tail0, config) - ) - } - - pragma[nomagic] - private predicate revFlowOut( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, - ApOption returnAp, Ap ap, Configuration config - ) { - exists(NodeEx out, boolean allowsFieldFlow | - revFlow(out, state, returnCtx, returnAp, ap, config) and - flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate revFlowParamToReturn( - ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap, Configuration config - ) { - revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), - pragma[only_bind_into](ap), pragma[only_bind_into](config)) and - parameterFlowThroughAllowed(p, pos.getKind()) and - PrevStage::parameterMayFlowThrough(p, getApprox(ap), config) - } - - pragma[nomagic] - private predicate revFlowThrough( - DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, - ApOption returnAp, Ap ap, Ap innerReturnAp, Configuration config - ) { - revFlowParamToReturn(p, state, pos, innerReturnAp, ap, config) and - revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned( - DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap, - Configuration config - ) { - exists(RetNodeEx ret, FlowState state, CcCall ccc | - revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap, config) and - returnFlowsThrough(ret, pos, state, ccc, _, _, ap, config) and - matchesCall(ccc, call) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Ap ap2, Content c | - PrevStage::storeStepCand(node1, _, tc, node2, contentType, config) and - revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _, config) and - revFlowConsCand(ap2, c, ap1, config) - ) - } - - predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(Ap ap1, Ap ap2 | - revFlow(node2, _, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and - readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _, - pragma[only_bind_into](config)) - ) - } - - additional predicate revFlow(NodeEx node, FlowState state, Configuration config) { - revFlow(node, state, _, _, _, config) - } - - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, _, _, ap, config) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, _, _, _, ap, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, Configuration config) { - revFlow(node, _, _, _, _, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, ap, config) - } - - private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepFwd(_, ap, tc, _, _, config) - } - - private predicate revConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepCand(_, ap, tc, _, _, config) - } - - private predicate validAp(Ap ap, Configuration config) { - revFlow(_, _, _, _, ap, config) and ap instanceof ApNil - or - exists(TypedContent head, Ap tail | - consCand(head, tail, config) and - ap = apCons(head, tail) - ) - } - - additional predicate consCand(TypedContent tc, Ap ap, Configuration config) { - revConsCand(tc, ap, config) and - validAp(ap, config) - } - - pragma[nomagic] - private predicate parameterFlowsThroughRev( - ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp, Configuration config - ) { - revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap, config) and - parameterFlowThroughAllowed(p, pos.getKind()) - } - - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(ReturnPosition pos | - returnFlowsThrough(_, pos, _, _, p, ap, _, config) and - parameterFlowsThroughRev(p, ap, pos, _, config) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - exists(ParamNodeEx p, ReturnPosition pos | - returnFlowsThrough(ret, pos, _, _, p, argAp, ap, config) and - parameterFlowsThroughRev(p, argAp, pos, ap, config) and - kind = pos.getKind() - ) - } - - pragma[nomagic] - private predicate revFlowThroughArg( - DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, - Ap ap, Configuration config - ) { - exists(ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp, config) - ) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | - revFlow(arg, state, returnCtx, returnAp, ap, config) and - revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, config)) and - fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and - states = count(FlowState state | fwdFlow(_, state, _, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | - fwdFlow(n, state, cc, summaryCtx, argAp, ap, config) - ) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, _, _, _, config)) and - fields = count(TypedContent f0 | consCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and - states = count(FlowState state | revFlow(_, state, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | - revFlow(n, state, returnCtx, retAp, ap, config) - ) - } - /* End: Stage logic. */ - } -} - -private module BooleanCallContext { - class Cc extends boolean { - Cc() { this in [true, false] } - } - - class CcCall extends Cc { - CcCall() { this = true } - } - - /** Holds if the call context may be `call`. */ - predicate matchesCall(CcCall cc, DataFlowCall call) { any() } - - class CcNoCall extends Cc { - CcNoCall() { this = false } - } - - Cc ccNone() { result = false } - - CcCall ccSomeCall() { result = true } - - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } -} - -private module Level1CallContext { - class Cc = CallContext; - - class CcCall = CallContextCall; - - pragma[inline] - predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } - - class CcNoCall = CallContextNoCall; - - Cc ccNone() { result instanceof CallContextAny } - - CcCall ccSomeCall() { result instanceof CallContextSomeCall } - - module NoLocalCallContext { - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSiteDispatch(call, c) - then result = TSpecificCall(call) - else result = TSomeCall() - } - } - - module LocalCallContext { - class LocalCc = LocalCallContext; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { - result = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - node.getEnclosingCallable()) - } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() - } - } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { - checkCallContextReturn(innercc, c, call) and - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() - } -} - -private module Stage2Param implements MkStage::StageParam { - private module PrevStage = Stage1; - - class Ap extends boolean { - Ap() { this in [true, false] } - } - - class ApNil extends Ap { - ApNil() { this = false } - } - - bindingset[result, ap] - PrevStage::Ap getApprox(Ap ap) { any() } - - ApNil getApNil(NodeEx node) { Stage1::revFlow(node, _) and exists(result) } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } - - class ApHeadContent = Unit; - - pragma[inline] - ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } - - ApHeadContent projectToHeadContent(Content c) { any() } - - class ApOption = BooleanOption; - - ApOption apNone() { result = TBooleanNone() } - - ApOption apSome(Ap ap) { result = TBooleanSome(ap) } - - import Level1CallContext - import NoLocalCallContext - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - ( - preservesValue = true and - localFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalStateStep(node1, state1, node2, state2, config) - ) and - exists(ap) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand1/6; - - predicate flowIntoCall = flowIntoCallNodeCand1/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - expectsContentEx(node, c) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - PrevStage::revFlowState(state, pragma[only_bind_into](config)) and - exists(ap) and - not stateBarrier(node, state, config) and - ( - notExpectsContent(node) - or - ap = true and - expectsContentCand(node, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage2 implements StageSig { - import MkStage::Stage -} - -pragma[nomagic] -private predicate flowOutOfCallNodeCand2( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -pragma[nomagic] -private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -private module LocalFlowBigStep { - /** - * A node where some checking is required, and hence the big-step relation - * is not allowed to step over. - */ - private class FlowCheckNode extends NodeEx { - FlowCheckNode() { - castNode(this.asNode()) or - clearsContentCached(this.asNode(), _) or - expectsContentCached(this.asNode(), _) - } - } - - /** - * Holds if `node` can be the first node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowEntry(NodeEx node, FlowState state, Configuration config) { - Stage2::revFlow(node, state, config) and - ( - sourceNode(node, state, config) - or - jumpStep(_, node, config) - or - additionalJumpStep(_, node, config) - or - additionalJumpStateStep(_, _, node, state, config) - or - node instanceof ParamNodeEx - or - node.asNode() instanceof OutNodeExt - or - Stage2::storeStepCand(_, _, _, node, _, config) - or - Stage2::readStepCand(_, _, node, config) - or - node instanceof FlowCheckNode - or - exists(FlowState s | - additionalLocalStateStep(_, s, node, state, config) and - s != state - ) - ) - } - - /** - * Holds if `node` can be the last node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowExit(NodeEx node, FlowState state, Configuration config) { - exists(NodeEx next | Stage2::revFlow(next, state, config) | - jumpStep(node, next, config) or - additionalJumpStep(node, next, config) or - flowIntoCallNodeCand2(_, node, next, _, config) or - flowOutOfCallNodeCand2(_, node, _, next, _, config) or - Stage2::storeStepCand(node, _, _, next, _, config) or - Stage2::readStepCand(node, _, next, config) - ) - or - exists(NodeEx next, FlowState s | Stage2::revFlow(next, s, config) | - additionalJumpStateStep(node, state, next, s, config) - or - additionalLocalStateStep(node, state, next, s, config) and - s != state - ) - or - Stage2::revFlow(node, state, config) and - node instanceof FlowCheckNode - or - sinkNode(node, state, config) - } - - pragma[noinline] - private predicate additionalLocalFlowStepNodeCand2( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, Configuration config - ) { - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 and - Stage2::revFlow(node1, pragma[only_bind_into](state1), false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false, - pragma[only_bind_into](config)) - or - additionalLocalStateStep(node1, state1, node2, state2, config) and - Stage2::revFlow(node1, state1, false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, state2, false, pragma[only_bind_into](config)) - } - - /** - * Holds if the local path from `node1` to `node2` is a prefix of a maximal - * subsequence of local flow steps in a dataflow path. - * - * This is the transitive closure of `[additional]localFlowStep` beginning - * at `localFlowEntry`. - */ - pragma[nomagic] - private predicate localFlowStepPlus( - NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, - Configuration config, LocalCallContext cc - ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and - ( - localFlowEntry(node1, pragma[only_bind_into](state), pragma[only_bind_into](config)) and - ( - localFlowStepNodeCand1(node1, node2, config) and - preservesValue = true and - t = node1.getDataFlowType() and // irrelevant dummy value - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - or - additionalLocalFlowStepNodeCand2(node1, state, node2, state, config) and - preservesValue = false and - t = node2.getDataFlowType() - ) and - node1 != node2 and - cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) - or - exists(NodeEx mid | - localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, - pragma[only_bind_into](config), cc) and - localFlowStepNodeCand1(mid, node2, config) and - not mid instanceof FlowCheckNode and - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - ) - or - exists(NodeEx mid | - localFlowStepPlus(node1, state, mid, _, _, pragma[only_bind_into](config), cc) and - additionalLocalFlowStepNodeCand2(mid, state, node2, state, config) and - not mid instanceof FlowCheckNode and - preservesValue = false and - t = node2.getDataFlowType() - ) - ) - } - - /** - * Holds if `node1` can step to `node2` in one or more local steps and this - * path can occur as a maximal subsequence of local steps in a dataflow path. - */ - pragma[nomagic] - predicate localFlowBigStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - DataFlowType t, Configuration config, LocalCallContext callContext - ) { - localFlowStepPlus(node1, state1, node2, preservesValue, t, config, callContext) and - localFlowExit(node2, state1, config) and - state1 = state2 - or - additionalLocalFlowStepNodeCand2(node1, state1, node2, state2, config) and - state1 != state2 and - preservesValue = false and - t = node2.getDataFlowType() and - callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) - } -} - -private import LocalFlowBigStep - -private module Stage3Param implements MkStage::StageParam { - private module PrevStage = Stage2; - - class Ap = ApproxAccessPathFront; - - class ApNil = ApproxAccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } - - class ApHeadContent = ContentApprox; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - predicate projectToHeadContent = getContentApprox/1; - - class ApOption = ApproxAccessPathFrontOption; - - ApOption apNone() { result = TApproxAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } - - import BooleanCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApproxAccessPathFrontNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand2/6; - - predicate flowIntoCall = flowIntoCallNodeCand2/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getAHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage3 implements StageSig { - import MkStage::Stage -} - -private module Stage4Param implements MkStage::StageParam { - private module PrevStage = Stage3; - - class Ap = AccessPathFront; - - class ApNil = AccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathFrontOption; - - ApOption apNone() { result = TAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } - - import BooleanCallContext - - pragma[nomagic] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) and - exists(lcc) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clearSet(NodeEx node, ContentSet c, Configuration config) { - PrevStage::revFlow(node, config) and - clearsContentCached(node.asNode(), c) - } - - pragma[nomagic] - private predicate clearContent(NodeEx node, Content c, Configuration config) { - exists(ContentSet cs | - PrevStage::readStepCand(_, pragma[only_bind_into](c), _, pragma[only_bind_into](config)) and - c = cs.getAReadContent() and - clearSet(node, cs, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clear(NodeEx node, Ap ap, Configuration config) { - clearContent(node, ap.getHead().getContent(), config) - } - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - not clear(node, ap, config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage4 implements StageSig { - import MkStage::Stage -} - -/** - * Holds if `argApf` is recorded as the summary context for flow reaching `node` - * and remains relevant for the following pruning stage. - */ -private predicate flowCandSummaryCtx( - NodeEx node, FlowState state, AccessPathFront argApf, Configuration config -) { - exists(AccessPathFront apf | - Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf, config) and - Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf, - config) - ) -} - -/** - * Holds if a length 2 access path approximation with the head `tc` is expected - * to be expensive. - */ -private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { - exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf, config)) and - nodes = - strictcount(NodeEx n, FlowState state | - Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - or - flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - ) and - accessPathApproxCostLimits(apLimit, tupleLimit) and - apLimit < tails and - tupleLimit < (tails - 1) * nodes and - not tc.forceHighPrecision() - ) -} - -private newtype TAccessPathApprox = - TNil(DataFlowType t) or - TConsNil(TypedContent tc, DataFlowType t) { - Stage4::consCand(tc, TFrontNil(t), _) and - not expensiveLen2unfolding(tc, _) - } or - TConsCons(TypedContent tc1, TypedContent tc2, int len) { - Stage4::consCand(tc1, TFrontHead(tc2), _) and - len in [2 .. accessPathLimit()] and - not expensiveLen2unfolding(tc1, _) - } or - TCons1(TypedContent tc, int len) { - len in [1 .. accessPathLimit()] and - expensiveLen2unfolding(tc, _) - } - -/** - * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only - * the first two elements of the list and its length are tracked. If data flows - * from a source to a given node with a given `AccessPathApprox`, this indicates - * the sequence of dereference operations needed to get from the value in the node - * to the tracked object. The final type indicates the type of the tracked object. - */ -abstract private class AccessPathApprox extends TAccessPathApprox { - abstract string toString(); - - abstract TypedContent getHead(); - - abstract int len(); - - abstract DataFlowType getType(); - - abstract AccessPathFront getFront(); - - /** Gets the access path obtained by popping `head` from this path, if any. */ - abstract AccessPathApprox pop(TypedContent head); -} - -private class AccessPathApproxNil extends AccessPathApprox, TNil { - private DataFlowType t; - - AccessPathApproxNil() { this = TNil(t) } - - override string toString() { result = concat(": " + ppReprType(t)) } - - override TypedContent getHead() { none() } - - override int len() { result = 0 } - - override DataFlowType getType() { result = t } - - override AccessPathFront getFront() { result = TFrontNil(t) } - - override AccessPathApprox pop(TypedContent head) { none() } -} - -abstract private class AccessPathApproxCons extends AccessPathApprox { } - -private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { - private TypedContent tc; - private DataFlowType t; - - AccessPathApproxConsNil() { this = TConsNil(tc, t) } - - override string toString() { - // The `concat` becomes "" if `ppReprType` has no result. - result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) - } - - override TypedContent getHead() { result = tc } - - override int len() { result = 1 } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } -} - -private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { - private TypedContent tc1; - private TypedContent tc2; - private int len; - - AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - - override string toString() { - if len = 2 - then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" - else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc1 } - - override int len() { result = len } - - override DataFlowType getType() { result = tc1.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc1) } - - override AccessPathApprox pop(TypedContent head) { - head = tc1 and - ( - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - } -} - -private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { - private TypedContent tc; - private int len; - - AccessPathApproxCons1() { this = TCons1(tc, len) } - - override string toString() { - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc } - - override int len() { result = len } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { - head = tc and - ( - exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2), _) | - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - or - exists(DataFlowType t | - len = 1 and - Stage4::consCand(tc, TFrontNil(t), _) and - result = TNil(t) - ) - ) - } -} - -/** Gets the access path obtained by popping `tc` from `ap`, if any. */ -private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } - -/** Gets the access path obtained by pushing `tc` onto `ap`. */ -private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } - -private newtype TAccessPathApproxOption = - TAccessPathApproxNone() or - TAccessPathApproxSome(AccessPathApprox apa) - -private class AccessPathApproxOption extends TAccessPathApproxOption { - string toString() { - this = TAccessPathApproxNone() and result = "" - or - this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) - } -} - -private module Stage5Param implements MkStage::StageParam { - private module PrevStage = Stage4; - - class Ap = AccessPathApprox; - - class ApNil = AccessPathApproxNil; - - pragma[nomagic] - PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathApproxOption; - - ApOption apNone() { result = TAccessPathApproxNone() } - - ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } - - import Level1CallContext - import LocalCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, lcc) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { any() } - - // Type checking is not necessary here as it has already been done in stage 3. - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage5 = MkStage::Stage; - -bindingset[conf, result] -private Configuration unbindConf(Configuration conf) { - exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c)) -} - -pragma[nomagic] -private predicate nodeMayUseSummary0( - NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(AccessPathApprox apa0 | - Stage5::parameterMayFlowThrough(p, _, _) and - Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0, config) and - Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), - TAccessPathApproxSome(apa), apa0, config) - ) -} - -pragma[nomagic] -private predicate nodeMayUseSummary( - NodeEx n, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::parameterMayFlowThrough(p, apa, config) and - nodeMayUseSummary0(n, p, state, apa, config) - ) -} - -private newtype TSummaryCtx = - TSummaryCtxNone() or - TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { - exists(Configuration config | - Stage5::parameterMayFlowThrough(p, ap.getApprox(), config) and - Stage5::revFlow(p, state, _, config) - ) - } - -/** - * A context for generating flow summaries. This represents flow entry through - * a specific parameter with an access path of a specific shape. - * - * Summaries are only created for parameters that may flow through. - */ -abstract private class SummaryCtx extends TSummaryCtx { - abstract string toString(); -} - -/** A summary context from which no flow summary can be generated. */ -private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { - override string toString() { result = "" } -} - -/** A summary context from which a flow summary can be generated. */ -private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParamNodeEx p; - private FlowState s; - private AccessPath ap; - - SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } - - ParameterPosition getParameterPos() { p.isParameterOf(_, result) } - - ParamNodeEx getParamNode() { result = p } - - override string toString() { result = p + ": " + ap } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** - * Gets the number of length 2 access path approximations that correspond to `apa`. - */ -private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { - exists(TypedContent tc, int len | - tc = apa.getHead() and - len = apa.len() and - result = - strictcount(AccessPathFront apf | - Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), - config) - ) - ) -} - -private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = - strictcount(NodeEx n, FlowState state | - Stage5::revFlow(n, state, apa, config) or nodeMayUseSummary(n, state, apa, config) - ) -} - -/** - * Holds if a length 2 access path approximation matching `apa` is expected - * to be expensive. - */ -private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = count1to2unfold(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - apLimit < aps and - tupleLimit < (aps - 1) * nodes - ) -} - -private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { - exists(TypedContent head | - apa.pop(head) = result and - Stage5::consCand(head, result, config) - ) -} - -/** - * Holds with `unfold = false` if a precise head-tail representation of `apa` is - * expected to be expensive. Holds with `unfold = true` otherwise. - */ -private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { - if apa.getHead().forceHighPrecision() - then unfold = true - else - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = countPotentialAps(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true - ) -} - -/** - * Gets the number of `AccessPath`s that correspond to `apa`. - */ -pragma[assume_small_delta] -private int countAps(AccessPathApprox apa, Configuration config) { - evalUnfold(apa, false, config) and - result = 1 and - (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) - or - evalUnfold(apa, false, config) and - result = count1to2unfold(apa, config) and - not expensiveLen1to2unfolding(apa, config) - or - evalUnfold(apa, true, config) and - result = countPotentialAps(apa, config) -} - -/** - * Gets the number of `AccessPath`s that would correspond to `apa` assuming - * that it is expanded to a precise head-tail representation. - */ -language[monotonicAggregates] -pragma[assume_small_delta] -private int countPotentialAps(AccessPathApprox apa, Configuration config) { - apa instanceof AccessPathApproxNil and result = 1 - or - result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) -} - -private newtype TAccessPath = - TAccessPathNil(DataFlowType t) or - TAccessPathCons(TypedContent head, AccessPath tail) { - exists(AccessPathApproxCons apa | - not evalUnfold(apa, false, _) and - head = apa.getHead() and - tail.getApprox() = getATail(apa, _) - ) - } or - TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - not expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head1 = apa.getHead() and - head2 = getATail(apa, _).getHead() - ) - } or - TAccessPathCons1(TypedContent head, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head = apa.getHead() - ) - } - -private newtype TPathNode = - pragma[assume_small_delta] - TPathNodeMid( - NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config - ) { - // A PathNode is introduced by a source ... - Stage5::revFlow(node, state, config) and - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - or - // ... or a step from an existing PathNode to another node. - exists(PathNodeMid mid | - pathStep(mid, node, state, cc, sc, ap) and - pragma[only_bind_into](config) = mid.getConfiguration() and - Stage5::revFlow(node, state, ap.getApprox(), pragma[only_bind_into](config)) - ) - } or - TPathNodeSink(NodeEx node, FlowState state, Configuration config) { - exists(PathNodeMid sink | - sink.isAtSink() and - node = sink.getNodeEx() and - state = sink.getState() and - config = sink.getConfiguration() - ) - } or - TPathNodeSourceGroup(string sourceGroup, Configuration config) { - exists(PathNodeImpl source | - sourceGroup = source.getSourceGroup() and - config = source.getConfiguration() - ) - } or - TPathNodeSinkGroup(string sinkGroup, Configuration config) { - exists(PathNodeSink sink | - sinkGroup = sink.getSinkGroup() and - config = sink.getConfiguration() - ) - } - -/** - * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a - * source to a given node with a given `AccessPath`, this indicates the sequence - * of dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ -private class AccessPath extends TAccessPath { - /** Gets the head of this access path, if any. */ - abstract TypedContent getHead(); - - /** Gets the tail of this access path, if any. */ - abstract AccessPath getTail(); - - /** Gets the front of this access path. */ - abstract AccessPathFront getFront(); - - /** Gets the approximation of this access path. */ - abstract AccessPathApprox getApprox(); - - /** Gets the length of this access path. */ - abstract int length(); - - /** Gets a textual representation of this access path. */ - abstract string toString(); - - /** Gets the access path obtained by popping `tc` from this access path, if any. */ - final AccessPath pop(TypedContent tc) { - result = this.getTail() and - tc = this.getHead() - } - - /** Gets the access path obtained by pushing `tc` onto this access path. */ - final AccessPath push(TypedContent tc) { this = result.pop(tc) } -} - -private class AccessPathNil extends AccessPath, TAccessPathNil { - private DataFlowType t; - - AccessPathNil() { this = TAccessPathNil(t) } - - DataFlowType getType() { result = t } - - override TypedContent getHead() { none() } - - override AccessPath getTail() { none() } - - override AccessPathFrontNil getFront() { result = TFrontNil(t) } - - override AccessPathApproxNil getApprox() { result = TNil(t) } - - override int length() { result = 0 } - - override string toString() { result = concat(": " + ppReprType(t)) } -} - -private class AccessPathCons extends AccessPath, TAccessPathCons { - private TypedContent head; - private AccessPath tail; - - AccessPathCons() { this = TAccessPathCons(head, tail) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { result = tail } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - pragma[assume_small_delta] - override AccessPathApproxCons getApprox() { - result = TConsNil(head, tail.(AccessPathNil).getType()) - or - result = TConsCons(head, tail.getHead(), this.length()) - or - result = TCons1(head, this.length()) - } - - pragma[assume_small_delta] - override int length() { result = 1 + tail.length() } - - private string toStringImpl(boolean needsSuffix) { - exists(DataFlowType t | - tail = TAccessPathNil(t) and - needsSuffix = false and - result = head.toString() + "]" + concat(" : " + ppReprType(t)) - ) - or - result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) - or - exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | - result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true - or - result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false - ) - or - exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | - result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true - or - result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false - ) - } - - override string toString() { - result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" - or - result = "[" + this.toStringImpl(false) - } -} - -private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { - private TypedContent head1; - private TypedContent head2; - private int len; - - AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } - - override TypedContent getHead() { result = head1 } - - override AccessPath getTail() { - Stage5::consCand(head1, result.getApprox(), _) and - result.getHead() = head2 and - result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head1) } - - override AccessPathApproxCons getApprox() { - result = TConsCons(head1, head2, len) or - result = TCons1(head1, len) - } - - override int length() { result = len } - - override string toString() { - if len = 2 - then result = "[" + head1.toString() + ", " + head2.toString() + "]" - else - result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" - } -} - -private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { - private TypedContent head; - private int len; - - AccessPathCons1() { this = TAccessPathCons1(head, len) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { - Stage5::consCand(head, result.getApprox(), _) and result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - - override int length() { result = len } - - override string toString() { - if len = 1 - then result = "[" + head.toString() + "]" - else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" - } -} - -abstract private class PathNodeImpl extends TPathNode { - /** Gets the `FlowState` of this node. */ - abstract FlowState getState(); - - /** Gets the associated configuration. */ - abstract Configuration getConfiguration(); - - /** Holds if this node is a source. */ - abstract predicate isSource(); - - abstract PathNodeImpl getASuccessorImpl(); - - private PathNodeImpl getASuccessorIfHidden() { - this.isHidden() and - result = this.getASuccessorImpl() - } - - pragma[nomagic] - private PathNodeImpl getANonHiddenSuccessor0() { - result = this.getASuccessorIfHidden*() and - not result.isHidden() - } - - final PathNodeImpl getANonHiddenSuccessor() { - result = this.getASuccessorImpl().getANonHiddenSuccessor0() and - not this.isHidden() - } - - abstract NodeEx getNodeEx(); - - predicate isHidden() { - not this.getConfiguration().includeHiddenNodes() and - ( - hiddenNode(this.getNodeEx().asNode()) and - not this.isSource() and - not this instanceof PathNodeSink - or - this.getNodeEx() instanceof TNodeImplicitRead - ) - } - - string getSourceGroup() { - this.isSource() and - this.getConfiguration().sourceGrouping(this.getNodeEx().asNode(), result) + not singleConfiguration() and + getConfig(state1).isAdditionalFlowStep(node1, node2) and + state2 = state1 } - predicate isFlowSource() { - this.isSource() and not exists(this.getSourceGroup()) - or - this instanceof PathNodeSourceGroup + predicate allowImplicitRead(Node node, ContentSet c) { + any(Configuration config).allowImplicitRead(node, c) } - predicate isFlowSink() { - this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or - this instanceof PathNodeSinkGroup - } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } - private string ppAp() { - this instanceof PathNodeSink and result = "" - or - exists(string s | s = this.(PathNodeMid).getAp().toString() | - if s = "" then result = "" else result = " " + s - ) - } + FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } - private string ppCtx() { - this instanceof PathNodeSink and result = "" - or - result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + predicate sourceGrouping(Node source, string sourceGroup) { + any(Configuration config).sourceGrouping(source, sourceGroup) } - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + predicate sinkGrouping(Node sink, string sinkGroup) { + any(Configuration config).sinkGrouping(sink, sinkGroup) } -} - -/** Holds if `n` can reach a sink. */ -private predicate directReach(PathNodeImpl n) { - n instanceof PathNodeSink or - n instanceof PathNodeSinkGroup or - directReach(n.getANonHiddenSuccessor()) -} - -/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ -private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } -/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { - n1.getANonHiddenSuccessor() = n2 and directReach(n2) + predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() } } -private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) +private import Impl as I +import I /** * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. */ -class PathNode instanceof PathNodeImpl { - PathNode() { reach(this) } - +class PathNode instanceof I::PathNode { /** Gets a textual representation of this element. */ final string toString() { result = super.toString() } @@ -3406,1548 +358,39 @@ class PathNode instanceof PathNodeImpl { } /** Gets the underlying `Node`. */ - final Node getNode() { super.getNodeEx().projectToNode() = result } + final Node getNode() { result = super.getNode() } /** Gets the `FlowState` of this node. */ - final FlowState getState() { result = super.getState() } + final FlowState getState() { result = getState(super.getState()) } /** Gets the associated configuration. */ - final Configuration getConfiguration() { result = super.getConfiguration() } + final Configuration getConfiguration() { result = getConfig(super.getState()) } /** Gets a successor of this node, if any. */ - final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } + final PathNode getASuccessor() { result = super.getASuccessor() } /** Holds if this node is a source. */ final predicate isSource() { super.isSource() } /** Holds if this node is a grouping of source nodes. */ - final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group, _) } + final predicate isSourceGroup(string group) { super.isSourceGroup(group) } /** Holds if this node is a grouping of sink nodes. */ - final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group, _) } + final predicate isSinkGroup(string group) { super.isSinkGroup(group) } } -/** - * Provides the query predicates needed to include a graph in a path-problem query. - */ -module PathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } - - /** Holds if `n` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode n, string key, string val) { - key = "semmle.label" and val = n.toString() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { - Subpaths::subpaths(arg, par, ret, out) - } -} - -/** - * An intermediate flow graph node. This is a triple consisting of a `Node`, - * a `CallContext`, and a `Configuration`. - */ -private class PathNodeMid extends PathNodeImpl, TPathNodeMid { - NodeEx node; - FlowState state; - CallContext cc; - SummaryCtx sc; - AccessPath ap; - Configuration config; - - PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - SummaryCtx getSummaryCtx() { result = sc } - - AccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - private PathNodeMid getSuccMid() { - pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx(), result.getAp()) and - result.getConfiguration() = unbindConf(this.getConfiguration()) - } - - override PathNodeImpl getASuccessorImpl() { - // an intermediate step to another intermediate node - result = this.getSuccMid() - or - // a final step to a sink - result = this.getSuccMid().projectToSink() - } - - override predicate isSource() { - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - } - - predicate isAtSink() { - sinkNode(node, state, config) and - ap instanceof AccessPathNil and - if hasSinkCallCtx(config) - then - // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` - // is exactly what we need to check. This also implies - // `sc instanceof SummaryCtxNone`. - // For `FeatureEqualSourceSinkCallContext` the initial call context was - // set to `CallContextSomeCall` and jumps are disallowed, so - // `cc instanceof CallContextNoCall` never holds. On the other hand, - // in this case there's never any need to enter a call except to identify - // a summary, so the condition in `pathIntoCallable` enforces this, which - // means that `sc instanceof SummaryCtxNone` holds if and only if we are - // in the call context of the source. - sc instanceof SummaryCtxNone or - cc instanceof CallContextNoCall - else any() - } - - PathNodeSink projectToSink() { - this.isAtSink() and - result.getNodeEx() = node and - result.getState() = state and - result.getConfiguration() = unbindConf(config) - } -} - -/** - * A flow graph node corresponding to a sink. This is disjoint from the - * intermediate nodes in order to uniquely correspond to a given sink by - * excluding the `CallContext`. - */ -private class PathNodeSink extends PathNodeImpl, TPathNodeSink { - NodeEx node; - FlowState state; - Configuration config; - - PathNodeSink() { this = TPathNodeSink(node, state, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result = TPathNodeSinkGroup(this.getSinkGroup(), config) - } - - override predicate isSource() { sourceNode(node, state, config) } - - string getSinkGroup() { config.sinkGrouping(node.asNode(), result) } -} - -private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { - string sourceGroup; - Configuration config; - - PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result.getSourceGroup() = sourceGroup and - result.getConfiguration() = config - } - - override predicate isSource() { none() } - - override string toString() { result = sourceGroup } - - override 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 - } -} - -private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { - string sinkGroup; - Configuration config; - - PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { none() } - - override predicate isSource() { none() } - - override string toString() { result = sinkGroup } - - override 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 - } -} - -private predicate pathNode( - PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, - Configuration conf, LocalCallContext localCC -) { - midnode = mid.getNodeEx() and - state = mid.getState() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - midnode.getEnclosingCallable()) and - ap = mid.getAp() -} - -/** - * Holds if data may flow from `mid` to `node`. The last step in or out of - * a callable is recorded by `cc`. - */ -pragma[assume_small_delta] -pragma[nomagic] -private predicate pathStep( - PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap -) { - exists(NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC | - pathNode(mid, midnode, state0, cc, sc, ap, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, true, _, conf, localCC) - ) - or - exists( - AccessPath ap0, NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC - | - pathNode(mid, midnode, state0, cc, sc, ap0, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), conf, - localCC) and - ap0 instanceof AccessPathNil - ) - or - jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - ap = mid.getAp() - or - additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, mid.getConfiguration()) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - pathIntoCallable(mid, node, state, _, cc, sc, _, _) and ap = mid.getAp() - or - pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone - or - pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() -} - -pragma[nomagic] -private predicate pathReadStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - tc = ap0.getHead() and - Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -pragma[nomagic] -private predicate pathStoreStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -private predicate pathOutOfCallable0( - PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa, - Configuration config -) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - apa = mid.getAp().getApprox() and - config = mid.getConfiguration() -} - -pragma[nomagic] -private predicate pathOutOfCallable1( - PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPathApprox apa, Configuration config -) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - pathOutOfCallable0(mid, pos, state, innercc, apa, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() +private predicate hasFlow(Node source, Node sink, Configuration config) { + exists(PathNode source0, PathNode sink0 | + hasFlowPath(source0, sink0, config) and + source0.getNode() = source and + sink0.getNode() = sink ) } -pragma[noinline] -private NodeEx getAnOutNodeFlow( - ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config -) { - result.asNode() = kind.getAnOutNode(call) and - Stage5::revFlow(result, _, apa, config) +private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) { + hasFlowPath(source, sink) and source.getConfiguration() = config } -/** - * Holds if data may flow from `mid` to `out`. The last step of this path - * is a return from a callable and is recorded by `cc`, if needed. - */ -pragma[noinline] -private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { - exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | - pathOutOfCallable1(mid, call, kind, state, cc, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} +private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) } -/** - * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. - */ -pragma[noinline] -private predicate pathIntoArg( - PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(ArgNodeEx arg, ArgumentPosition apos | - pathNode(mid, arg, state, cc, _, ap, config, _) and - arg.asNode().(ArgNode).argumentOf(call, apos) and - apa = ap.getApprox() and - parameterMatch(ppos, apos) - ) -} - -pragma[nomagic] -private predicate parameterCand( - DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::revFlow(p, _, apa, config) and - p.isParameterOf(callable, pos) - ) -} - -pragma[nomagic] -private predicate pathIntoCallable0( - PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, AccessPath ap, Configuration config -) { - exists(AccessPathApprox apa | - pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - callable = resolveCall(call, outercc) and - parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa), - pragma[only_bind_into](config)) - ) -} - -/** - * Holds if data may flow from `mid` to `p` through `call`. The contexts - * before and after entering the callable are `outercc` and `innercc`, - * respectively. - */ -pragma[nomagic] -private predicate pathIntoCallable( - PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, - SummaryCtx sc, DataFlowCall call, Configuration config -) { - exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | - pathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - ( - sc = TSummaryCtxSome(p, state, ap) - or - not exists(TSummaryCtxSome(p, state, ap)) and - sc = TSummaryCtxNone() and - // When the call contexts of source and sink needs to match then there's - // never any reason to enter a callable except to find a summary. See also - // the comment in `PathNodeMid::isAtSink`. - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) -} - -/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ -pragma[nomagic] -private predicate paramFlowsThrough( - ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, - AccessPathApprox apa, Configuration config -) { - exists(RetNodeEx ret | - pathNode(_, ret, state, cc, sc, ap, config, _) and - kind = ret.getKind() and - apa = ap.getApprox() and - parameterFlowThroughAllowed(sc.getParamNode(), kind) - ) -} - -pragma[nomagic] -private predicate pathThroughCallable0( - DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(CallContext innercc, SummaryCtx sc | - pathIntoCallable(mid, _, _, cc, innercc, sc, call, config) and - paramFlowsThrough(kind, state, innercc, sc, ap, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` through a callable to the node `out`. - * The context `cc` is restored to its value prior to entering the callable. - */ -pragma[noinline] -private predicate pathThroughCallable( - PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap -) { - exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config | - pathThroughCallable0(call, mid, kind, state, cc, ap, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -private module Subpaths { - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths01( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - exists(Configuration config | - pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and - pathIntoCallable(arg, par, _, _, innercc, sc, _, config) and - paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, - pragma[only_bind_into](apout), _, unbindConf(config)) and - not arg.isHidden() - ) - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `sout`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths02( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and - out.asNode() = kind.getAnOutNode(_) - } - - pragma[nomagic] - private Configuration getPathNodeConf(PathNodeImpl n) { result = n.getConfiguration() } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple. - */ - pragma[nomagic] - private predicate subpaths03( - PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, AccessPath apout - ) { - exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | - subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and - pathNode(ret, retnode, sout, innercc, sc, apout, unbindConf(getPathNodeConf(arg)), _) and - kind = retnode.getKind() - ) - } - - private PathNodeImpl localStepToHidden(PathNodeImpl n) { - n.getASuccessorImpl() = result and - result.isHidden() and - exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | - localFlowBigStep(n1, _, n2, _, _, _, _, _) or - store(n1, _, n2, _, _) or - readSet(n1, _, n2, _) - ) - } - - pragma[nomagic] - private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { - succ = pred.getANonHiddenSuccessor() and - succNode = succ.getNodeEx() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { - exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | - pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and - subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and - hasSuccessor(pragma[only_bind_into](arg), par, p) and - not ret.isHidden() and - pathNode(out0, o, sout, _, _, apout, _, _) - | - out = out0 or out = out0.projectToSink() - ) - } - - /** - * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. - */ - predicate retReach(PathNodeImpl n) { - exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) - or - exists(PathNodeImpl mid | - retReach(mid) and - n.getANonHiddenSuccessor() = mid and - not subpaths(_, mid, _, _) - ) - } -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -private predicate hasFlowPath( - PathNodeImpl flowsource, PathNodeImpl flowsink, Configuration configuration -) { - flowsource.isFlowSource() and - flowsource.getConfiguration() = configuration and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.isFlowSink() -} - -private predicate flowsTo( - PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink, - Configuration configuration -) { - flowsource.isSource() and - flowsource.getConfiguration() = configuration and - flowsource.getNodeEx().asNode() = source and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.getNodeEx().asNode() = sink -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -predicate flowsTo(Node source, Node sink, Configuration configuration) { - flowsTo(_, _, source, sink, configuration) -} - -private predicate finalStats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples -) { - fwd = true and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and - tuples = count(PathNodeImpl pn) - or - fwd = false and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and - tuples = count(PathNode pn) -} - -/** - * INTERNAL: Only for debugging. - * - * Calculates per-stage metrics for data flow. - */ -predicate stageStats( - int n, string stage, int nodes, int fields, int conscand, int states, int tuples, - Configuration config -) { - stage = "1 Fwd" and - n = 10 and - Stage1::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "1 Rev" and - n = 15 and - Stage1::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Fwd" and - n = 20 and - Stage2::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Rev" and - n = 25 and - Stage2::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Fwd" and - n = 30 and - Stage3::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Rev" and - n = 35 and - Stage3::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Fwd" and - n = 40 and - Stage4::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Rev" and - n = 45 and - Stage4::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Fwd" and - n = 50 and - Stage5::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Rev" and - n = 55 and - Stage5::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) - or - stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) -} - -private module FlowExploration { - private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - jumpStep(node1, node2, config) - or - additionalJumpStep(node1, node2, config) - or - additionalJumpStateStep(node1, _, node2, _, config) - or - // flow into callable - viableParamArgEx(_, node2, node1) - or - // flow out of a callable - viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) - | - c1 = node1.getEnclosingCallable() and - c2 = node2.getEnclosingCallable() and - c1 != c2 - ) - } - - private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSource(n) or config.isSource(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSrc(mid, config) and callableStep(mid, c, config) - ) - } - - private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSink(n) or config.isSink(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSink(mid, config) and callableStep(c, mid, config) - ) - } - - private newtype TCallableExt = - TCallable(DataFlowCallable c, Configuration config) { - interestingCallableSrc(c, config) or - interestingCallableSink(c, config) - } or - TCallableSrc() or - TCallableSink() - - private predicate callableExtSrc(TCallableSrc src) { any() } - - private predicate callableExtSink(TCallableSink sink) { any() } - - private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { - exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | - callableStep(c1, c2, config) and - ce1 = TCallable(c1, pragma[only_bind_into](config)) and - ce2 = TCallable(c2, pragma[only_bind_into](config)) - ) - or - exists(Node n, Configuration config | - ce1 = TCallableSrc() and - (config.isSource(n) or config.isSource(n, _)) and - ce2 = TCallable(getNodeEnclosingCallable(n), config) - ) - or - exists(Node n, Configuration config | - ce2 = TCallableSink() and - (config.isSink(n) or config.isSink(n, _)) and - ce1 = TCallable(getNodeEnclosingCallable(n), config) - ) - } - - private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { - callableExtStepFwd(ce2, ce1) - } - - private int distSrcExt(TCallableExt c) = - shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) - - private int distSinkExt(TCallableExt c) = - shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) - - private int distSrc(DataFlowCallable c, Configuration config) { - result = distSrcExt(TCallable(c, config)) - 1 - } - - private int distSink(DataFlowCallable c, Configuration config) { - result = distSinkExt(TCallable(c, config)) - 1 - } - - private newtype TPartialAccessPath = - TPartialNil(DataFlowType t) or - TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first - * element of the list and its length are tracked. If data flows from a source to - * a given node with a given `AccessPath`, this indicates the sequence of - * dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ - private class PartialAccessPath extends TPartialAccessPath { - abstract string toString(); - - TypedContent getHead() { this = TPartialCons(result, _) } - - int len() { - this = TPartialNil(_) and result = 0 - or - this = TPartialCons(_, result) - } - - DataFlowType getType() { - this = TPartialNil(result) - or - exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) - } - } - - private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { - override string toString() { - exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) - } - } - - private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { - override string toString() { - exists(TypedContent tc, int len | this = TPartialCons(tc, len) | - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private newtype TRevPartialAccessPath = - TRevPartialNil() or - TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `Content`s, but only the first - * element of the list and its length are tracked. - */ - private class RevPartialAccessPath extends TRevPartialAccessPath { - abstract string toString(); - - Content getHead() { this = TRevPartialCons(result, _) } - - int len() { - this = TRevPartialNil() and result = 0 - or - this = TRevPartialCons(_, result) - } - } - - private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { - override string toString() { result = "" } - } - - private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { - override string toString() { - exists(Content c, int len | this = TRevPartialCons(c, len) | - if len = 1 - then result = "[" + c.toString() + "]" - else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private predicate relevantState(FlowState state) { - sourceNode(_, state, _) or - sinkNode(_, state, _) or - additionalLocalStateStep(_, state, _, _, _) or - additionalLocalStateStep(_, _, _, state, _) or - additionalJumpStateStep(_, state, _, _, _) or - additionalJumpStateStep(_, _, _, state, _) - } - - private newtype TSummaryCtx1 = - TSummaryCtx1None() or - TSummaryCtx1Param(ParamNodeEx p) - - private newtype TSummaryCtx2 = - TSummaryCtx2None() or - TSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TSummaryCtx3 = - TSummaryCtx3None() or - TSummaryCtx3Some(PartialAccessPath ap) - - private newtype TRevSummaryCtx1 = - TRevSummaryCtx1None() or - TRevSummaryCtx1Some(ReturnPosition pos) - - private newtype TRevSummaryCtx2 = - TRevSummaryCtx2None() or - TRevSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TRevSummaryCtx3 = - TRevSummaryCtx3None() or - TRevSummaryCtx3Some(RevPartialAccessPath ap) - - private newtype TPartialPathNode = - TPartialPathNodeFwd( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = TPartialNil(node.getDataFlowType()) and - exists(config.explorationLimit()) - or - partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap, config) and - distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() - } or - TPartialPathNodeRev( - NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, - RevPartialAccessPath ap, Configuration config - ) { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() and - exists(config.explorationLimit()) - or - revPartialPathStep(_, node, state, sc1, sc2, sc3, ap, config) and - not clearsContentEx(node, ap.getHead()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead()) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() - } - - pragma[nomagic] - private predicate partialPathNodeMk0( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap, config) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - not clearsContentEx(node, ap.getHead().getContent()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead().getContent()) - ) and - if node.asNode() instanceof CastingNode - then compatibleTypes(node.getDataFlowType(), ap.getType()) - else any() - } - - /** - * A `Node` augmented with a call context, an access path, and a configuration. - */ - class PartialPathNode extends TPartialPathNode { - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() - } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - /** Gets the underlying `Node`. */ - final Node getNode() { this.getNodeEx().projectToNode() = result } - - FlowState getState() { none() } - - private NodeEx getNodeEx() { - result = this.(PartialPathNodeFwd).getNodeEx() or - result = this.(PartialPathNodeRev).getNodeEx() - } - - /** Gets the associated configuration. */ - Configuration getConfiguration() { none() } - - /** Gets a successor of this node, if any. */ - PartialPathNode getASuccessor() { none() } - - /** - * Gets the approximate distance to the nearest source measured in number - * of interprocedural steps. - */ - int getSourceDistance() { - result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - /** - * Gets the approximate distance to the nearest sink measured in number - * of interprocedural steps. - */ - int getSinkDistance() { - result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - private string ppAp() { - exists(string s | - s = this.(PartialPathNodeFwd).getAp().toString() or - s = this.(PartialPathNodeRev).getAp().toString() - | - if s = "" then result = "" else result = " " + s - ) - } - - private string ppCtx() { - result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" - } - - /** Holds if this is a source in a forward-flow path. */ - predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } - - /** Holds if this is a sink in a reverse-flow path. */ - predicate isRevSink() { this.(PartialPathNodeRev).isSink() } - } - - /** - * Provides the query predicates needed to include a graph in a path-problem query. - */ - module PartialPathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } - } - - private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { - NodeEx node; - FlowState state; - CallContext cc; - TSummaryCtx1 sc1; - TSummaryCtx2 sc2; - TSummaryCtx3 sc3; - PartialAccessPath ap; - Configuration config; - - PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - TSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TSummaryCtx3 getSummaryCtx3() { result = sc3 } - - PartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeFwd getASuccessor() { - partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp(), - result.getConfiguration()) - } - - predicate isSource() { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap instanceof TPartialNil - } - } - - private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { - NodeEx node; - FlowState state; - TRevSummaryCtx1 sc1; - TRevSummaryCtx2 sc2; - TRevSummaryCtx3 sc3; - RevPartialAccessPath ap; - Configuration config; - - PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } - - RevPartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeRev getASuccessor() { - revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), - this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp(), this.getConfiguration()) - } - - predicate isSink() { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() - } - } - - private predicate partialPathStep( - PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, - TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and - ( - localFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - ) - or - jumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - partialPathStoreStep(mid, _, _, node, ap) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(PartialAccessPath ap0, TypedContent tc | - partialPathReadStep(mid, ap0, tc, node, cc, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsFwd(ap, tc, ap0, config) - ) - or - partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap, config) - or - partialPathOutOfCallable(mid, node, state, cc, ap, config) and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() - or - partialPathThroughCallable(mid, node, state, cc, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - bindingset[result, i] - private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } - - pragma[inline] - private predicate partialPathStoreStep( - PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, - PartialAccessPath ap2 - ) { - exists(NodeEx midNode, DataFlowType contentType | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - store(midNode, tc, node, contentType, mid.getConfiguration()) and - ap2.getHead() = tc and - ap2.len() = unbindInt(ap1.len() + 1) and - compatibleTypes(ap1.getType(), contentType) - ) - } - - pragma[nomagic] - private predicate apConsFwd( - PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeFwd mid | - partialPathStoreStep(mid, ap1, tc, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate partialPathReadStep( - PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc, - Configuration config - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and - ap.getHead() = tc and - pragma[only_bind_into](config) = mid.getConfiguration() and - cc = mid.getCallContext() - ) - } - - private predicate partialPathOutOfCallable0( - PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, - PartialAccessPath ap, Configuration config - ) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - ap = mid.getAp() and - config = mid.getConfiguration() - } - - pragma[nomagic] - private predicate partialPathOutOfCallable1( - PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - partialPathOutOfCallable0(mid, pos, state, innercc, ap, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) - } - - private predicate partialPathOutOfCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(ReturnKindExt kind, DataFlowCall call | - partialPathOutOfCallable1(mid, call, kind, state, cc, ap, config) - | - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[noinline] - private predicate partialPathIntoArg( - PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ArgNode arg, ArgumentPosition apos | - arg = mid.getNodeEx().asNode() and - state = mid.getState() and - cc = mid.getCallContext() and - arg.argumentOf(call, apos) and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate partialPathIntoCallable0( - PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - partialPathIntoArg(mid, pos, state, outercc, call, ap, config) and - callable = resolveCall(call, outercc) - } - - private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, - CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ParameterPosition pos, DataFlowCallable callable | - partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - sc1 = TSummaryCtx1Param(p) and - sc2 = TSummaryCtx2Some(state) and - sc3 = TSummaryCtx3Some(ap) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) - } - - pragma[nomagic] - private predicate paramFlowsThroughInPartialPath( - ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeFwd mid, RetNodeEx ret | - mid.getNodeEx() = ret and - kind = ret.getKind() and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() and - ap = mid.getAp() - ) - } - - pragma[noinline] - private predicate partialPathThroughCallable0( - DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | - partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _, config) and - paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap, config) - ) - } - - private predicate partialPathThroughCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ReturnKindExt kind | - partialPathThroughCallable0(call, mid, kind, state, cc, ap, config) and - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[nomagic] - private predicate revPartialPathStep( - PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, - TRevSummaryCtx3 sc3, RevPartialAccessPath ap, Configuration config - ) { - localFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - jumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - revPartialPathReadStep(mid, _, _, node, ap) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(RevPartialAccessPath ap0, Content c | - revPartialPathStoreStep(mid, ap0, c, node, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsRev(ap, c, ap0, config) - ) - or - exists(ParamNodeEx p | - mid.getNodeEx() = p and - viableParamArgEx(_, p, node) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - or - exists(ReturnPosition pos | - revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap, config) and - pos = getReturnPosition(node.asNode()) - ) - or - revPartialPathThroughCallable(mid, node, state, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - pragma[inline] - private predicate revPartialPathReadStep( - PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, - RevPartialAccessPath ap2 - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - read(node, c, midNode, mid.getConfiguration()) and - ap2.getHead() = c and - ap2.len() = unbindInt(ap1.len() + 1) - ) - } - - pragma[nomagic] - private predicate apConsRev( - RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeRev mid | - revPartialPathReadStep(mid, ap1, c, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathStoreStep( - PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config - ) { - exists(NodeEx midNode, TypedContent tc | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - store(node, tc, midNode, _, config) and - ap.getHead() = c and - config = mid.getConfiguration() and - tc.getContent() = c - ) - } - - pragma[nomagic] - private predicate revPartialPathIntoReturn( - PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, - TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap, - Configuration config - ) { - exists(NodeEx out | - mid.getNodeEx() = out and - mid.getState() = state and - viableReturnPosOutEx(call, pos, out) and - sc1 = TRevSummaryCtx1Some(pos) and - sc2 = TRevSummaryCtx2Some(state) and - sc3 = TRevSummaryCtx3Some(ap) and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathFlowsThrough( - ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, - TRevSummaryCtx3Some sc3, RevPartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | - mid.getNodeEx() = p and - mid.getState() = state and - p.getPosition() = ppos and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable0( - DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, - RevPartialAccessPath ap, Configuration config - ) { - exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | - revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _, config) and - revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap, config) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ArgumentPosition pos | - revPartialPathThroughCallable0(call, mid, pos, state, ap, config) and - node.asNode().(ArgNode).argumentOf(call, pos) - ) - } -} - -import FlowExploration - -private predicate partialFlow( - PartialPathNode source, PartialPathNode node, Configuration configuration -) { - source.getConfiguration() = configuration and - source.isFwdSource() and - node = source.getASuccessor+() -} - -private predicate revPartialFlow( - PartialPathNode node, PartialPathNode sink, Configuration configuration -) { - sink.getConfiguration() = configuration and - sink.isRevSink() and - node.getASuccessor+() = sink -} +predicate flowsTo = hasFlow/3; diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/tainttracking1/TaintTracking.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/tainttracking1/TaintTracking.qll new file mode 100644 index 00000000000..7333264298e --- /dev/null +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/tainttracking1/TaintTracking.qll @@ -0,0 +1,63 @@ +/** + * Provides classes for performing local (intra-procedural) and + * global (inter-procedural) taint-tracking analyses. + */ + +import TaintTrackingParameter::Public +private import TaintTrackingParameter::Private + +private module AddTaintDefaults implements +DataFlowInternal::FullStateConfigSig { + import Config + + predicate isBarrier(DataFlow::Node node) { + Config::isBarrier(node) or defaultTaintSanitizer(node) + } + + predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + Config::isAdditionalFlowStep(node1, node2) or + defaultAdditionalTaintStep(node1, node2) + } + + predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) { + Config::allowImplicitRead(node, c) + or + ( + Config::isSink(node, _) or + Config::isAdditionalFlowStep(node, _) or + Config::isAdditionalFlowStep(node, _, _, _) + ) and + defaultImplicitTaintRead(node, c) + } +} + +/** + * Constructs a standard taint tracking computation. + */ +module Make implements DataFlow::DataFlowSig { + private module Config0 implements DataFlowInternal::FullStateConfigSig { + import DataFlowInternal::DefaultState + import Config + } + + private module C implements DataFlowInternal::FullStateConfigSig { + import AddTaintDefaults + } + + import DataFlowInternal::Impl +} + +/** + * Constructs a taint tracking computation using flow state. + */ +module MakeWithState implements DataFlow::DataFlowSig { + private module Config0 implements DataFlowInternal::FullStateConfigSig { + import Config + } + + private module C implements DataFlowInternal::FullStateConfigSig { + import AddTaintDefaults + } + + import DataFlowInternal::Impl +} diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/tainttracking1/TaintTrackingParameter.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/tainttracking1/TaintTrackingParameter.qll index 7fd632efb0e..0d73207dad9 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/tainttracking1/TaintTrackingParameter.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/tainttracking1/TaintTrackingParameter.qll @@ -2,4 +2,5 @@ import semmle.code.cpp.dataflow.internal.TaintTrackingUtil as Public module Private { import semmle.code.cpp.dataflow.DataFlow::DataFlow as DataFlow + import semmle.code.cpp.dataflow.internal.DataFlowImpl as DataFlowInternal } diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/new/DataFlow.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/new/DataFlow.qll index fd643e72387..c14ea8b7e90 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/new/DataFlow.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/new/DataFlow.qll @@ -26,5 +26,6 @@ import cpp * global (inter-procedural) data flow analyses. */ module DataFlow { - import semmle.code.cpp.ir.dataflow.internal.DataFlowImpl + import semmle.code.cpp.ir.dataflow.internal.DataFlow + import semmle.code.cpp.ir.dataflow.internal.DataFlowImpl1 } diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/new/TaintTracking.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/new/TaintTracking.qll index 92b396e466d..23cef94c1c3 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/new/TaintTracking.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/new/TaintTracking.qll @@ -23,5 +23,6 @@ import semmle.code.cpp.dataflow.new.DataFlow2 * global (inter-procedural) taint-tracking analyses. */ module TaintTracking { + import semmle.code.cpp.ir.dataflow.internal.tainttracking1.TaintTracking import semmle.code.cpp.ir.dataflow.internal.tainttracking1.TaintTrackingImpl } diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/DataFlow.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/DataFlow.qll index b3d8827a9b2..32c236fec2c 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/DataFlow.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/DataFlow.qll @@ -22,5 +22,6 @@ import cpp module DataFlow { - import semmle.code.cpp.ir.dataflow.internal.DataFlowImpl + import semmle.code.cpp.ir.dataflow.internal.DataFlow + import semmle.code.cpp.ir.dataflow.internal.DataFlowImpl1 } diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/TaintTracking.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/TaintTracking.qll index 7eada0bb244..f3449904420 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/TaintTracking.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/TaintTracking.qll @@ -19,5 +19,6 @@ import semmle.code.cpp.ir.dataflow.DataFlow import semmle.code.cpp.ir.dataflow.DataFlow2 module TaintTracking { + import semmle.code.cpp.ir.dataflow.internal.tainttracking1.TaintTracking import semmle.code.cpp.ir.dataflow.internal.tainttracking1.TaintTrackingImpl } diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlow.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlow.qll new file mode 100644 index 00000000000..fe9b9b18941 --- /dev/null +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlow.qll @@ -0,0 +1,245 @@ +/** + * Provides an implementation of global (interprocedural) data flow. This file + * re-exports the local (intraprocedural) data flow analysis from + * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed + * through the `Make` and `MakeWithState` modules. + */ + +private import DataFlowImplCommon +private import DataFlowImplSpecific::Private +import DataFlowImplSpecific::Public +import DataFlowImplCommonPublic +private import DataFlowImpl + +/** An input configuration for data flow. */ +signature module ConfigSig { + /** + * Holds if `source` is a relevant data flow source. + */ + predicate isSource(Node source); + + /** + * Holds if `sink` is a relevant data flow sink. + */ + predicate isSink(Node sink); + + /** + * Holds if data flow through `node` is prohibited. This completely removes + * `node` from the data flow graph. + */ + default predicate isBarrier(Node node) { none() } + + /** Holds if data flow into `node` is prohibited. */ + default predicate isBarrierIn(Node node) { none() } + + /** Holds if data flow out of `node` is prohibited. */ + default predicate isBarrierOut(Node node) { none() } + + /** + * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. + */ + default predicate isAdditionalFlowStep(Node node1, Node node2) { none() } + + /** + * Holds if an arbitrary number of implicit read steps of content `c` may be + * taken at `node`. + */ + default predicate allowImplicitRead(Node node, ContentSet c) { none() } + + /** + * Gets the virtual dispatch branching limit when calculating field flow. + * This can be overridden to a smaller value to improve performance (a + * value of 0 disables field flow), or a larger value to get more results. + */ + default int fieldFlowBranchLimit() { result = 2 } + + /** + * Gets a data flow configuration feature to add restrictions to the set of + * valid flow paths. + * + * - `FeatureHasSourceCallContext`: + * Assume that sources have some existing call context to disallow + * conflicting return-flow directly following the source. + * - `FeatureHasSinkCallContext`: + * Assume that sinks have some existing call context to disallow + * conflicting argument-to-parameter flow directly preceding the sink. + * - `FeatureEqualSourceSinkCallContext`: + * Implies both of the above and additionally ensures that the entire flow + * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. + */ + default FlowFeature getAFeature() { none() } + + /** Holds if sources should be grouped in the result of `hasFlowPath`. */ + default predicate sourceGrouping(Node source, string sourceGroup) { none() } + + /** Holds if sinks should be grouped in the result of `hasFlowPath`. */ + default predicate sinkGrouping(Node sink, string sinkGroup) { none() } + + /** + * Holds if hidden nodes should be included in the data flow graph. + * + * This feature should only be used for debugging or when the data flow graph + * is not visualized (as it is in a `path-problem` query). + */ + default predicate includeHiddenNodes() { none() } +} + +/** An input configuration for data flow using flow state. */ +signature module StateConfigSig { + bindingset[this] + class FlowState; + + /** + * Holds if `source` is a relevant data flow source with the given initial + * `state`. + */ + predicate isSource(Node source, FlowState state); + + /** + * Holds if `sink` is a relevant data flow sink accepting `state`. + */ + predicate isSink(Node sink, FlowState state); + + /** + * Holds if data flow through `node` is prohibited. This completely removes + * `node` from the data flow graph. + */ + default predicate isBarrier(Node node) { none() } + + /** + * Holds if data flow through `node` is prohibited when the flow state is + * `state`. + */ + predicate isBarrier(Node node, FlowState state); + + /** Holds if data flow into `node` is prohibited. */ + default predicate isBarrierIn(Node node) { none() } + + /** Holds if data flow out of `node` is prohibited. */ + default predicate isBarrierOut(Node node) { none() } + + /** + * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. + */ + default predicate isAdditionalFlowStep(Node node1, Node node2) { none() } + + /** + * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. + * This step is only applicable in `state1` and updates the flow state to `state2`. + */ + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2); + + /** + * Holds if an arbitrary number of implicit read steps of content `c` may be + * taken at `node`. + */ + default predicate allowImplicitRead(Node node, ContentSet c) { none() } + + /** + * Gets the virtual dispatch branching limit when calculating field flow. + * This can be overridden to a smaller value to improve performance (a + * value of 0 disables field flow), or a larger value to get more results. + */ + default int fieldFlowBranchLimit() { result = 2 } + + /** + * Gets a data flow configuration feature to add restrictions to the set of + * valid flow paths. + * + * - `FeatureHasSourceCallContext`: + * Assume that sources have some existing call context to disallow + * conflicting return-flow directly following the source. + * - `FeatureHasSinkCallContext`: + * Assume that sinks have some existing call context to disallow + * conflicting argument-to-parameter flow directly preceding the sink. + * - `FeatureEqualSourceSinkCallContext`: + * Implies both of the above and additionally ensures that the entire flow + * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. + */ + default FlowFeature getAFeature() { none() } + + /** Holds if sources should be grouped in the result of `hasFlowPath`. */ + default predicate sourceGrouping(Node source, string sourceGroup) { none() } + + /** Holds if sinks should be grouped in the result of `hasFlowPath`. */ + default predicate sinkGrouping(Node sink, string sinkGroup) { none() } + + /** + * Holds if hidden nodes should be included in the data flow graph. + * + * This feature should only be used for debugging or when the data flow graph + * is not visualized (as it is in a `path-problem` query). + */ + default predicate includeHiddenNodes() { none() } +} + +/** + * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` + * measured in approximate number of interprocedural steps. + */ +signature int explorationLimitSig(); + +/** + * The output of a data flow computation. + */ +signature module DataFlowSig { + /** + * A `Node` augmented with a call context (except for sinks) and an access path. + * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. + */ + class PathNode; + + /** + * Holds if data can flow from `source` to `sink`. + * + * The corresponding paths are generated from the end-points and the graph + * included in the module `PathGraph`. + */ + predicate hasFlowPath(PathNode source, PathNode sink); + + /** + * Holds if data can flow from `source` to `sink`. + */ + predicate hasFlow(Node source, Node sink); + + /** + * Holds if data can flow from some source to `sink`. + */ + predicate hasFlowTo(Node sink); + + /** + * Holds if data can flow from some source to `sink`. + */ + predicate hasFlowToExpr(DataFlowExpr sink); +} + +/** + * Constructs a standard data flow computation. + */ +module Make implements DataFlowSig { + private module C implements FullStateConfigSig { + import DefaultState + import Config + } + + import Impl +} + +/** + * Constructs a data flow computation using flow state. + */ +module MakeWithState implements DataFlowSig { + private module C implements FullStateConfigSig { + import Config + } + + import Impl +} diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll index 1b969756b09..0afcba55582 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll @@ -1,135 +1,75 @@ /** - * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from - * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed - * through the `Configuration` class. This file exists in several identical - * copies, allowing queries to use multiple `Configuration` classes that depend - * on each other without introducing mutual recursion among those configurations. + * INTERNAL: Do not use. + * + * Provides an implementation of global (interprocedural) data flow. */ private import DataFlowImplCommon private import DataFlowImplSpecific::Private -import DataFlowImplSpecific::Public -import DataFlowImplCommonPublic +private import DataFlowImplSpecific::Public +private import DataFlowImplCommonPublic +import DataFlow /** - * A configuration of interprocedural data flow analysis. This defines - * sources, sinks, and any other configurable aspect of the analysis. Each - * use of the global data flow library must define its own unique extension - * of this abstract class. To create a configuration, extend this class with - * a subclass whose characteristic predicate is a unique singleton string. - * For example, write - * - * ```ql - * class MyAnalysisConfiguration extends DataFlow::Configuration { - * MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" } - * // Override `isSource` and `isSink`. - * // Optionally override `isBarrier`. - * // Optionally override `isAdditionalFlowStep`. - * } - * ``` - * Conceptually, this defines a graph where the nodes are `DataFlow::Node`s and - * the edges are those data-flow steps that preserve the value of the node - * along with any additional edges defined by `isAdditionalFlowStep`. - * Specifying nodes in `isBarrier` will remove those nodes from the graph, and - * specifying nodes in `isBarrierIn` and/or `isBarrierOut` will remove in-going - * and/or out-going edges from those nodes, respectively. - * - * Then, to query whether there is flow between some `source` and `sink`, - * write - * - * ```ql - * exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink)) - * ``` - * - * Multiple configurations can coexist, but two classes extending - * `DataFlow::Configuration` should never depend on each other. One of them - * should instead depend on a `DataFlow2::Configuration`, a - * `DataFlow3::Configuration`, or a `DataFlow4::Configuration`. + * An input configuration for data flow using flow state. This signature equals + * `StateConfigSig`, but requires explicit implementation of all predicates. */ -abstract class Configuration extends string { +signature module FullStateConfigSig { bindingset[this] - Configuration() { any() } - - /** - * Holds if `source` is a relevant data flow source. - */ - predicate isSource(Node source) { none() } + class FlowState; /** * Holds if `source` is a relevant data flow source with the given initial * `state`. */ - predicate isSource(Node source, FlowState state) { none() } - - /** - * Holds if `sink` is a relevant data flow sink. - */ - predicate isSink(Node sink) { none() } + predicate isSource(Node source, FlowState state); /** * Holds if `sink` is a relevant data flow sink accepting `state`. */ - predicate isSink(Node sink, FlowState state) { none() } + predicate isSink(Node sink, FlowState state); /** * Holds if data flow through `node` is prohibited. This completely removes * `node` from the data flow graph. */ - predicate isBarrier(Node node) { none() } + predicate isBarrier(Node node); /** * Holds if data flow through `node` is prohibited when the flow state is * `state`. */ - predicate isBarrier(Node node, FlowState state) { none() } + predicate isBarrier(Node node, FlowState state); /** Holds if data flow into `node` is prohibited. */ - predicate isBarrierIn(Node node) { none() } + predicate isBarrierIn(Node node); /** Holds if data flow out of `node` is prohibited. */ - predicate isBarrierOut(Node node) { none() } - - /** - * DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead. - * - * Holds if data flow through nodes guarded by `guard` is prohibited. - */ - deprecated predicate isBarrierGuard(BarrierGuard guard) { none() } - - /** - * DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead. - * - * Holds if data flow through nodes guarded by `guard` is prohibited when - * the flow state is `state` - */ - deprecated predicate isBarrierGuard(BarrierGuard guard, FlowState state) { none() } + predicate isBarrierOut(Node node); /** * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. */ - predicate isAdditionalFlowStep(Node node1, Node node2) { none() } + predicate isAdditionalFlowStep(Node node1, Node node2); /** * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. * This step is only applicable in `state1` and updates the flow state to `state2`. */ - predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { - none() - } + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2); /** * Holds if an arbitrary number of implicit read steps of content `c` may be * taken at `node`. */ - predicate allowImplicitRead(Node node, ContentSet c) { none() } + predicate allowImplicitRead(Node node, ContentSet c); /** * Gets the virtual dispatch branching limit when calculating field flow. * This can be overridden to a smaller value to improve performance (a * value of 0 disables field flow), or a larger value to get more results. */ - int fieldFlowBranchLimit() { result = 2 } + int fieldFlowBranchLimit(); /** * Gets a data flow configuration feature to add restrictions to the set of @@ -144,1720 +84,680 @@ abstract class Configuration extends string { * - `FeatureEqualSourceSinkCallContext`: * Implies both of the above and additionally ensures that the entire flow * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. */ - FlowFeature getAFeature() { none() } + FlowFeature getAFeature(); /** Holds if sources should be grouped in the result of `hasFlowPath`. */ - predicate sourceGrouping(Node source, string sourceGroup) { none() } + predicate sourceGrouping(Node source, string sourceGroup); /** Holds if sinks should be grouped in the result of `hasFlowPath`. */ - predicate sinkGrouping(Node sink, string sinkGroup) { none() } - - /** - * Holds if data may flow from `source` to `sink` for this configuration. - */ - predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } - - /** - * Holds if data may flow from `source` to `sink` for this configuration. - * - * The corresponding paths are generated from the end-points and the graph - * included in the module `PathGraph`. - */ - predicate hasFlowPath(PathNode source, PathNode sink) { hasFlowPath(source, sink, this) } - - /** - * Holds if data may flow from some source to `sink` for this configuration. - */ - predicate hasFlowTo(Node sink) { - sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode() - } - - /** - * Holds if data may flow from some source to `sink` for this configuration. - */ - predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } - - /** - * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` - * measured in approximate number of interprocedural steps. - */ - int explorationLimit() { none() } + predicate sinkGrouping(Node sink, string sinkGroup); /** * Holds if hidden nodes should be included in the data flow graph. * * This feature should only be used for debugging or when the data flow graph - * is not visualized (for example in a `path-problem` query). + * is not visualized (as it is in a `path-problem` query). */ - predicate includeHiddenNodes() { none() } + predicate includeHiddenNodes(); +} - /** - * Holds if there is a partial data flow path from `source` to `node`. The - * approximate distance between `node` and the closest source is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards sink definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sources is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - */ - final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { - partialFlow(source, node, this) and - dist = node.getSourceDistance() - } +/** + * Provides default `FlowState` implementations given a `StateConfigSig`. + */ +module DefaultState { + class FlowState = Unit; - /** - * Holds if there is a partial data flow path from `node` to `sink`. The - * approximate distance between `node` and the closest sink is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards source definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sinks is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - * - * Note that reverse flow has slightly lower precision than the corresponding - * forward flow, as reverse flow disregards type pruning among other features. - */ - final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { - revPartialFlow(node, sink, this) and - dist = node.getSinkDistance() + predicate isSource(Node source, FlowState state) { Config::isSource(source) and exists(state) } + + predicate isSink(Node sink, FlowState state) { Config::isSink(sink) and exists(state) } + + predicate isBarrier(Node node, FlowState state) { none() } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + none() } } /** - * This class exists to prevent mutual recursion between the user-overridden - * member predicates of `Configuration` and the rest of the data-flow library. - * Good performance cannot be guaranteed in the presence of such recursion, so - * it should be replaced by using more than one copy of the data flow library. + * Constructs a data flow computation given a full input configuration. */ -abstract private class ConfigurationRecursionPrevention extends Configuration { - bindingset[this] - ConfigurationRecursionPrevention() { any() } +module Impl { + private class FlowState = Config::FlowState; - override predicate hasFlow(Node source, Node sink) { - strictcount(Node n | this.isSource(n)) < 0 - or - strictcount(Node n | this.isSource(n, _)) < 0 - or - strictcount(Node n | this.isSink(n)) < 0 - or - strictcount(Node n | this.isSink(n, _)) < 0 - or - strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0 - or - strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, _, n2, _)) < 0 - or - super.hasFlow(source, sink) - } -} + private newtype TNodeEx = + TNodeNormal(Node n) or + TNodeImplicitRead(Node n, boolean hasRead) { + Config::allowImplicitRead(n, _) and hasRead = [false, true] + } -private newtype TNodeEx = - TNodeNormal(Node n) or - TNodeImplicitRead(Node n, boolean hasRead) { - any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true] - } - -private class NodeEx extends TNodeEx { - string toString() { - result = this.asNode().toString() - or - exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") - } - - Node asNode() { this = TNodeNormal(result) } - - predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } - - Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } - - pragma[nomagic] - private DataFlowCallable getEnclosingCallable0() { - nodeEnclosingCallable(this.projectToNode(), result) - } - - pragma[inline] - DataFlowCallable getEnclosingCallable() { - pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) - } - - pragma[nomagic] - private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } - - pragma[inline] - DataFlowType getDataFlowType() { - pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) - } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -private class ArgNodeEx extends NodeEx { - ArgNodeEx() { this.asNode() instanceof ArgNode } -} - -private class ParamNodeEx extends NodeEx { - ParamNodeEx() { this.asNode() instanceof ParamNode } - - predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { - this.asNode().(ParamNode).isParameterOf(c, pos) - } - - ParameterPosition getPosition() { this.isParameterOf(_, result) } -} - -private class RetNodeEx extends NodeEx { - RetNodeEx() { this.asNode() instanceof ReturnNodeExt } - - ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } - - ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } -} - -private predicate inBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierIn(n) - | - config.isSource(n) or config.isSource(n, _) - ) -} - -private predicate outBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierOut(n) - | - config.isSink(n) or config.isSink(n, _) - ) -} - -/** A bridge class to access the deprecated `isBarrierGuard`. */ -private class BarrierGuardGuardedNodeBridge extends Unit { - abstract predicate guardedNode(Node n, Configuration config); - - abstract predicate guardedNode(Node n, FlowState state, Configuration config); -} - -private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { - deprecated override predicate guardedNode(Node n, Configuration config) { - exists(BarrierGuard g | - config.isBarrierGuard(g) and - n = g.getAGuardedNode() - ) - } - - deprecated override predicate guardedNode(Node n, FlowState state, Configuration config) { - exists(BarrierGuard g | - config.isBarrierGuard(g, state) and - n = g.getAGuardedNode() - ) - } -} - -pragma[nomagic] -private predicate fullBarrier(NodeEx node, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n) - or - config.isBarrierIn(n) and - not config.isSource(n) and - not config.isSource(n, _) - or - config.isBarrierOut(n) and - not config.isSink(n) and - not config.isSink(n, _) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config) - ) -} - -pragma[nomagic] -private predicate stateBarrier(NodeEx node, FlowState state, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n, state) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config) - ) -} - -pragma[nomagic] -private predicate sourceNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSource(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSource(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -pragma[nomagic] -private predicate sinkNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSink(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSink(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -/** Provides the relevant barriers for a step from `node1` to `node2`. */ -pragma[inline] -private predicate stepFilter(NodeEx node1, NodeEx node2, Configuration config) { - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) -} - -/** - * Holds if data can flow in one local step from `node1` to `node2`. - */ -private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.asNode() = n and - node2.isImplicitReadNode(n, false) and - not fullBarrier(node1, config) - ) -} - -/** - * Holds if the additional step from `node1` to `node2` does not jump between callables. - */ -private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.isImplicitReadNode(n, true) and - node2.asNode() = n and - not fullBarrier(node2, config) - ) -} - -private predicate additionalLocalStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) - ) -} - -/** - * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. - */ -private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if the additional step from `node1` to `node2` jumps between callables. - */ -private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate additionalJumpStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -pragma[nomagic] -private predicate readSet(NodeEx node1, ContentSet c, NodeEx node2, Configuration config) { - readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and - stepFilter(node1, node2, config) - or - exists(Node n | - node2.isImplicitReadNode(n, true) and - node1.isImplicitReadNode(n, _) and - config.allowImplicitRead(n, c) - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(ContentSet cs | - readSet(node1, cs, node2, config) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate clearsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - clearsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate expectsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - expectsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -pragma[nomagic] -private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } - -pragma[nomagic] -private predicate hasReadStep(Content c, Configuration config) { read(_, c, _, config) } - -pragma[nomagic] -private predicate store( - NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config -) { - store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), - contentType) and - hasReadStep(tc.getContent(), config) and - stepFilter(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { - viableReturnPosOut(call, pos, out.asNode()) -} - -pragma[nomagic] -private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { - viableParamArg(call, p.asNode(), arg.asNode()) -} - -/** - * Holds if field flow should be used for the given configuration. - */ -private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } - -private predicate hasSourceCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSourceCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate hasSinkCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSinkCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if flow from `p` to a return node of kind `kind` is allowed. - * - * We don't expect a parameter to return stored in itself, unless - * explicitly allowed - */ -bindingset[p, kind] -private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { - exists(ParameterPosition pos | p.isParameterOf(_, pos) | - not kind.(ParamUpdateReturnKind).getPosition() = pos - or - allowParameterReturnInSelfCached(p.asNode()) - ) -} - -private module Stage1 implements StageSig { - class Ap extends int { - // workaround for bad functionality-induced joins (happens when using `Unit`) - pragma[nomagic] - Ap() { this in [0 .. 1] and this < 1 } - } - - private class Cc = boolean; - - /* Begin: Stage 1 logic. */ - /** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `cc` records whether the node is reached through an - * argument in a call. - */ - private predicate fwdFlow(NodeEx node, Cc cc, Configuration config) { - sourceNode(node, _, config) and - if hasSourceCallCtx(config) then cc = true else cc = false - or - exists(NodeEx mid | fwdFlow(mid, cc, config) | - localFlowStep(mid, node, config) or - additionalLocalFlowStep(mid, node, config) or - additionalLocalStateStep(mid, _, node, _, config) - ) - or - exists(NodeEx mid | fwdFlow(mid, _, config) and cc = false | - jumpStep(mid, node, config) or - additionalJumpStep(mid, node, config) or - additionalJumpStateStep(mid, _, node, _, config) - ) - or - // store - exists(NodeEx mid | - useFieldFlow(config) and - fwdFlow(mid, cc, config) and - store(mid, _, node, _, config) - ) - or - // read - exists(ContentSet c | - fwdFlowReadSet(c, node, cc, config) and - fwdFlowConsCandSet(c, _, config) - ) - or - // flow into a callable - fwdFlowIn(_, _, _, node, config) and - cc = true - or - // flow out of a callable - fwdFlowOut(_, node, false, config) and - cc = false - or - // flow through a callable - exists(DataFlowCall call | - fwdFlowOutFromArg(call, node, config) and - fwdFlowIsEntered(call, cc, config) - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowIn( - DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p, Configuration config - ) { - // call context cannot help reduce virtual dispatch - fwdFlow(arg, cc, config) and - viableParamArgEx(call, p, arg) and - not fullBarrier(p, config) and - ( - cc = false + private class NodeEx extends TNodeEx { + string toString() { + result = this.asNode().toString() or - cc = true and - not reducedViableImplInCallContext(call, _, _) - ) - or - // call context may help reduce virtual dispatch - exists(DataFlowCallable target | - fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target, config) and - target = viableImplInSomeFwdFlowCallContextExt(call, config) and - cc = true - ) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - fwdFlowIn(call, _, cc, _, config) - } - - pragma[nomagic] - private predicate fwdFlowInReducedViableImplInSomeCallContext( - DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target, Configuration config - ) { - fwdFlow(arg, true, config) and - viableParamArgEx(call, p, arg) and - reducedViableImplInCallContext(call, _, _) and - target = p.getEnclosingCallable() and - not fullBarrier(p, config) - } - - /** - * Gets a viable dispatch target of `call` in the context `ctx`. This is - * restricted to those `call`s for which a context might make a difference, - * and to `ctx`s that are reachable in `fwdFlow`. - */ - pragma[nomagic] - private DataFlowCallable viableImplInSomeFwdFlowCallContextExt( - DataFlowCall call, Configuration config - ) { - exists(DataFlowCall ctx | - fwdFlowIsEntered(ctx, _, config) and - result = viableImplInCallContextExt(call, ctx) - ) - } - - private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) } - - pragma[nomagic] - private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc, Configuration config) { - exists(NodeEx mid | - fwdFlow(mid, cc, config) and - readSet(mid, c, node, config) - ) - } - - /** - * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - fwdFlow(mid, _, config) and - store(mid, tc, node, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `cs` may be interpreted in a read as the target of some store - * into `c`, in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCandSet(ContentSet cs, Content c, Configuration config) { - fwdFlowConsCand(c, config) and - c = cs.getAReadContent() - } - - pragma[nomagic] - private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { - exists(RetNodeEx ret | - fwdFlow(ret, cc, config) and - ret.getReturnPosition() = pos - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) { - exists(ReturnPosition pos | - fwdFlowReturnPosition(pos, cc, config) and - viableReturnPosOutEx(call, pos, out) and - not fullBarrier(out, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) { - fwdFlowOut(call, out, true, config) - } - - private predicate stateStepFwd(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1 | - additionalLocalStateStep(node1, state1, _, state2, config) or - additionalJumpStateStep(node1, state1, _, state2, config) - | - fwdFlow(node1, config) - ) - } - - private predicate fwdFlowState(FlowState state, Configuration config) { - sourceNode(_, state, config) - or - exists(FlowState state0 | - fwdFlowState(state0, config) and - stateStepFwd(state0, state, config) - ) - } - - /** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink. - */ - pragma[nomagic] - private predicate revFlow(NodeEx node, boolean toReturn, Configuration config) { - revFlow0(node, toReturn, config) and - fwdFlow(node, config) - } - - pragma[nomagic] - private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) { - exists(FlowState state | - fwdFlow(node, pragma[only_bind_into](config)) and - sinkNode(node, state, config) and - fwdFlowState(state, pragma[only_bind_into](config)) and - if hasSinkCallCtx(config) then toReturn = true else toReturn = false - ) - or - exists(NodeEx mid | revFlow(mid, toReturn, config) | - localFlowStep(node, mid, config) or - additionalLocalFlowStep(node, mid, config) or - additionalLocalStateStep(node, _, mid, _, config) - ) - or - exists(NodeEx mid | revFlow(mid, _, config) and toReturn = false | - jumpStep(node, mid, config) or - additionalJumpStep(node, mid, config) or - additionalJumpStateStep(node, _, mid, _, config) - ) - or - // store - exists(Content c | - revFlowStore(c, node, toReturn, config) and - revFlowConsCand(c, config) - ) - or - // read - exists(NodeEx mid, ContentSet c | - readSet(node, c, mid, config) and - fwdFlowConsCandSet(c, _, pragma[only_bind_into](config)) and - revFlow(mid, toReturn, pragma[only_bind_into](config)) - ) - or - // flow into a callable - revFlowIn(_, node, false, config) and - toReturn = false - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(pos, config) and - node.(RetNodeEx).getReturnPosition() = pos and - toReturn = true - ) - or - // flow through a callable - exists(DataFlowCall call | - revFlowInToReturn(call, node, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - /** - * Holds if `c` is the target of a read in the flow covered by `revFlow`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, ContentSet cs | - fwdFlow(node, pragma[only_bind_into](config)) and - readSet(node, cs, mid, config) and - fwdFlowConsCandSet(cs, c, pragma[only_bind_into](config)) and - revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) { - exists(NodeEx mid, TypedContent tc | - revFlow(mid, toReturn, pragma[only_bind_into](config)) and - fwdFlowConsCand(c, pragma[only_bind_into](config)) and - store(node, tc, mid, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `c` is the target of both a read and a store in the flow covered - * by `revFlow`. - */ - pragma[nomagic] - additional predicate revFlowIsReadAndStored(Content c, Configuration conf) { - revFlowConsCand(c, conf) and - revFlowStore(c, _, _, conf) - } - - pragma[nomagic] - additional predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config - ) { - fwdFlowReturnPosition(pos, _, config) and - viableReturnPosOutEx(call, pos, out) - } - - pragma[nomagic] - private predicate revFlowOut(ReturnPosition pos, Configuration config) { - exists(NodeEx out | - revFlow(out, _, config) and - viableReturnPosOutNodeCandFwd1(_, pos, out, config) - ) - } - - pragma[nomagic] - additional predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config - ) { - fwdFlowIn(call, arg, _, p, config) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate revFlowIn( - DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config - ) { - exists(ParamNodeEx p | - revFlow(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) { - revFlowIn(call, arg, true, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(NodeEx out | - revFlow(out, toReturn, config) and - fwdFlowOutFromArg(call, out, config) - ) - } - - private predicate stateStepRev(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - additionalLocalStateStep(node1, state1, node2, state2, config) or - additionalJumpStateStep(node1, state1, node2, state2, config) - | - revFlow(node1, _, pragma[only_bind_into](config)) and - revFlow(node2, _, pragma[only_bind_into](config)) and - fwdFlowState(state1, pragma[only_bind_into](config)) and - fwdFlowState(state2, pragma[only_bind_into](config)) - ) - } - - additional predicate revFlowState(FlowState state, Configuration config) { - exists(NodeEx node | - sinkNode(node, state, config) and - revFlow(node, _, pragma[only_bind_into](config)) and - fwdFlowState(state, pragma[only_bind_into](config)) - ) - or - exists(FlowState state0 | - revFlowState(state0, config) and - stateStepRev(state, state0, config) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Content c | - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - revFlow(node2, pragma[only_bind_into](config)) and - store(node1, tc, node2, contentType, config) and - c = tc.getContent() and - exists(ap1) - ) - } - - pragma[nomagic] - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) { - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - read(n1, c, n2, pragma[only_bind_into](config)) and - revFlow(n2, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, config) and - exists(ap) - } - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, _, pragma[only_bind_into](config)) and - exists(state) and - exists(ap) - } - - private predicate throughFlowNodeCand(NodeEx node, Configuration config) { - revFlow(node, true, config) and - fwdFlow(node, true, config) and - not inBarrier(node, config) and - not outBarrier(node, config) - } - - /** Holds if flow may return from `callable`. */ - pragma[nomagic] - private predicate returnFlowCallableNodeCand( - DataFlowCallable callable, ReturnKindExt kind, Configuration config - ) { - exists(RetNodeEx ret | - throughFlowNodeCand(ret, config) and - callable = ret.getEnclosingCallable() and - kind = ret.getKind() - ) - } - - /** - * Holds if flow may enter through `p` and reach a return node making `p` a - * candidate for the origin of a summary. - */ - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(DataFlowCallable c, ReturnKindExt kind | - throughFlowNodeCand(p, config) and - returnFlowCallableNodeCand(c, kind, config) and - p.getEnclosingCallable() = c and - exists(ap) and - parameterFlowThroughAllowed(p, kind) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - throughFlowNodeCand(ret, config) and - kind = ret.getKind() and - exists(argAp) and - exists(ap) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, boolean toReturn | - revFlow(arg, toReturn, config) and - revFlowInToReturn(call, arg, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, config)) and - fields = count(Content f0 | fwdFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | fwdFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, config)) and - fields = count(Content f0 | revFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | revFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | revFlow(n, b, config)) - } - /* End: Stage 1 logic. */ -} - -pragma[noinline] -private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - localFlowStep(node1, node2, config) -} - -pragma[noinline] -private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - additionalLocalFlowStep(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCand1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config -) { - Stage1::revFlow(out, config) and - Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, Configuration config -) { - exists(ReturnPosition pos | - viableReturnPosOutNodeCand1(call, pos, out, config) and - pos = ret.getReturnPosition() and - kind = pos.getKind() and - Stage1::revFlow(ret, config) and - not outBarrier(ret, config) and - not inBarrier(out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCand1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config -) { - Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and - Stage1::revFlow(arg, config) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config -) { - viableParamArgNodeCand1(call, p, arg, config) and - Stage1::revFlow(p, config) and - not outBarrier(arg, config) and - not inBarrier(p, config) -} - -/** - * Gets the amount of forward branching on the origin of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int branch(NodeEx n1, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n1, _, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) - ) -} - -/** - * Gets the amount of backward branching on the target of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int join(NodeEx n2, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n, _, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) - ) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. The - * `allowsFieldFlow` flag indicates whether the branching is within the limit - * specified by the configuration. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, ret, kind, out, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(ret, pragma[only_bind_into](config)) and - j = join(out, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. The `allowsFieldFlow` flag indicates whether - * the branching is within the limit specified by the configuration. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config -) { - flowIntoCallNodeCand1(call, arg, p, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(arg, pragma[only_bind_into](config)) and - j = join(p, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -private signature module StageSig { - class Ap; - - predicate revFlow(NodeEx node, Configuration config); - - predicate revFlowAp(NodeEx node, Ap ap, Configuration config); - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config); - - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config); - - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config); - - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ); - - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ); - - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config); -} - -private module MkStage { - class ApApprox = PrevStage::Ap; - - signature module StageParam { - class Ap; - - class ApNil extends Ap; - - bindingset[result, ap] - ApApprox getApprox(Ap ap); - - ApNil getApNil(NodeEx node); - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail); - - /** - * An approximation of `Content` that corresponds to the precision level of - * `Ap`, such that the mappings from both `Ap` and `Content` to this type - * are functional. - */ - class ApHeadContent; - - ApHeadContent getHeadContent(Ap ap); - - ApHeadContent projectToHeadContent(Content c); - - class ApOption; - - ApOption apNone(); - - ApOption apSome(Ap ap); - - class Cc; - - class CcCall extends Cc; - - // TODO: member predicate on CcCall - predicate matchesCall(CcCall cc, DataFlowCall call); - - class CcNoCall extends Cc; - - Cc ccNone(); - - CcCall ccSomeCall(); - - class LocalCc; - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc); - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ); - - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config - ); - - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config - ); - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config); - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType); - } - - module Stage implements StageSig { - import Param - - /* Begin: Stage logic. */ - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - private predicate revFlowApAlias(NodeEx node, ApApprox apa, Configuration config) { - PrevStage::revFlowAp(node, apa, config) + exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") } - pragma[nomagic] - private predicate flowIntoCallApa( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa, - Configuration config - ) { - flowIntoCall(call, arg, p, allowsFieldFlow, config) and - PrevStage::revFlowAp(p, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(arg, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } + Node asNode() { this = TNodeNormal(result) } + + predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } + + Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } pragma[nomagic] - private predicate flowOutOfCallApa( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - ApApprox apa, Configuration config - ) { - flowOutOfCall(call, ret, kind, out, allowsFieldFlow, config) and - PrevStage::revFlowAp(out, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(ret, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowThroughOutOfCall( - DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, - ApApprox argApa, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa, pragma[only_bind_into](config)) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) and - matchesCall(ccc, call) - ) - } - - /** - * Holds if `node` is reachable with access path `ap` from a source in the - * configuration `config`. - * - * The call context `cc` records whether the node is reached through an - * argument in a call, and if so, `summaryCtx` and `argAp` record the - * corresponding parameter position and access path of that argument, respectively. - */ - pragma[nomagic] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa, config) and - PrevStage::revFlow(node, state, apa, config) and - filter(node, state, ap, config) + private DataFlowCallable getEnclosingCallable0() { + nodeEnclosingCallable(this.projectToNode(), result) } pragma[inline] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, cc, summaryCtx, argAp, ap, _, config) + DataFlowCallable getEnclosingCallable() { + pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) } pragma[nomagic] - private predicate fwdFlow0( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config + private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } + + pragma[inline] + DataFlowType getDataFlowType() { + pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) + } + + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn ) { - sourceNode(node, state, config) and - (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and - argAp = apNone() and - summaryCtx = TParamNodeNone() and - ap = getApNil(node) and - apa = getApprox(ap) + this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + private class ArgNodeEx extends NodeEx { + ArgNodeEx() { this.asNode() instanceof ArgNode } + } + + private class ParamNodeEx extends NodeEx { + ParamNodeEx() { this.asNode() instanceof ParamNode } + + predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { + this.asNode().(ParamNode).isParameterOf(c, pos) + } + + ParameterPosition getPosition() { this.isParameterOf(_, result) } + } + + private class RetNodeEx extends NodeEx { + RetNodeEx() { this.asNode() instanceof ReturnNodeExt } + + ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } + + ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } + } + + private predicate inBarrier(NodeEx node) { + exists(Node n | + node.asNode() = n and + Config::isBarrierIn(n) and + Config::isSource(n, _) + ) + } + + private predicate outBarrier(NodeEx node) { + exists(Node n | + node.asNode() = n and + Config::isBarrierOut(n) and + Config::isSink(n, _) + ) + } + + pragma[nomagic] + private predicate fullBarrier(NodeEx node) { + exists(Node n | node.asNode() = n | + Config::isBarrier(n) or - exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | - fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0, config) and - localCc = getLocalCc(mid, cc) - | - localStep(mid, state0, node, state, true, _, config, localCc) and - ap = ap0 and - apa = apa0 - or - localStep(mid, state0, node, state, false, ap, config, localCc) and - ap0 instanceof ApNil and - apa = getApprox(ap) + Config::isBarrierIn(n) and + not Config::isSource(n, _) + or + Config::isBarrierOut(n) and + not Config::isSink(n, _) + ) + } + + pragma[nomagic] + private predicate stateBarrier(NodeEx node, FlowState state) { + exists(Node n | node.asNode() = n | Config::isBarrier(n, state)) + } + + pragma[nomagic] + private predicate sourceNode(NodeEx node, FlowState state) { + Config::isSource(node.asNode(), state) and + not fullBarrier(node) and + not stateBarrier(node, state) + } + + pragma[nomagic] + private predicate sinkNode(NodeEx node, FlowState state) { + Config::isSink(node.asNode(), state) and + not fullBarrier(node) and + not stateBarrier(node, state) + } + + /** Provides the relevant barriers for a step from `node1` to `node2`. */ + pragma[inline] + private predicate stepFilter(NodeEx node1, NodeEx node2) { + not outBarrier(node1) and + not inBarrier(node2) and + not fullBarrier(node1) and + not fullBarrier(node2) + } + + /** + * Holds if data can flow in one local step from `node1` to `node2`. + */ + private predicate localFlowStepEx(NodeEx node1, NodeEx node2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and + stepFilter(node1, node2) + ) + or + exists(Node n | + Config::allowImplicitRead(n, _) and + node1.asNode() = n and + node2.isImplicitReadNode(n, false) and + not fullBarrier(node1) + ) + } + + /** + * Holds if the additional step from `node1` to `node2` does not jump between callables. + */ + private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + Config::isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and + getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and + stepFilter(node1, node2) + ) + or + exists(Node n | + Config::allowImplicitRead(n, _) and + node1.isImplicitReadNode(n, true) and + node2.asNode() = n and + not fullBarrier(node2) + ) + } + + private predicate additionalLocalStateStep(NodeEx node1, FlowState s1, NodeEx node2, FlowState s2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + Config::isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and + getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and + stepFilter(node1, node2) and + not stateBarrier(node1, s1) and + not stateBarrier(node2, s2) + ) + } + + /** + * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. + */ + private predicate jumpStepEx(NodeEx node1, NodeEx node2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and + stepFilter(node1, node2) and + not Config::getAFeature() instanceof FeatureEqualSourceSinkCallContext + ) + } + + /** + * Holds if the additional step from `node1` to `node2` jumps between callables. + */ + private predicate additionalJumpStep(NodeEx node1, NodeEx node2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + Config::isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and + getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and + stepFilter(node1, node2) and + not Config::getAFeature() instanceof FeatureEqualSourceSinkCallContext + ) + } + + private predicate additionalJumpStateStep(NodeEx node1, FlowState s1, NodeEx node2, FlowState s2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + Config::isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and + getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and + stepFilter(node1, node2) and + not stateBarrier(node1, s1) and + not stateBarrier(node2, s2) and + not Config::getAFeature() instanceof FeatureEqualSourceSinkCallContext + ) + } + + pragma[nomagic] + private predicate readSetEx(NodeEx node1, ContentSet c, NodeEx node2) { + readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and + stepFilter(node1, node2) + or + exists(Node n | + node2.isImplicitReadNode(n, true) and + node1.isImplicitReadNode(n, _) and + Config::allowImplicitRead(n, c) + ) + } + + // inline to reduce fan-out via `getAReadContent` + bindingset[c] + private predicate read(NodeEx node1, Content c, NodeEx node2) { + exists(ContentSet cs | + readSetEx(node1, cs, node2) and + pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() + ) + } + + // inline to reduce fan-out via `getAReadContent` + bindingset[c] + private predicate clearsContentEx(NodeEx n, Content c) { + exists(ContentSet cs | + clearsContentCached(n.asNode(), cs) and + pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() + ) + } + + // inline to reduce fan-out via `getAReadContent` + bindingset[c] + private predicate expectsContentEx(NodeEx n, Content c) { + exists(ContentSet cs | + expectsContentCached(n.asNode(), cs) and + pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() + ) + } + + pragma[nomagic] + private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } + + pragma[nomagic] + private predicate hasReadStep(Content c) { read(_, c, _) } + + pragma[nomagic] + private predicate storeEx(NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType) { + store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), + contentType) and + hasReadStep(tc.getContent()) and + stepFilter(node1, node2) + } + + pragma[nomagic] + private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { + viableReturnPosOut(call, pos, out.asNode()) + } + + pragma[nomagic] + private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { + viableParamArg(call, p.asNode(), arg.asNode()) + } + + /** + * Holds if field flow should be used for the given configuration. + */ + private predicate useFieldFlow() { Config::fieldFlowBranchLimit() >= 1 } + + private predicate hasSourceCallCtx() { + exists(FlowFeature feature | feature = Config::getAFeature() | + feature instanceof FeatureHasSourceCallContext or + feature instanceof FeatureEqualSourceSinkCallContext + ) + } + + private predicate hasSinkCallCtx() { + exists(FlowFeature feature | feature = Config::getAFeature() | + feature instanceof FeatureHasSinkCallContext or + feature instanceof FeatureEqualSourceSinkCallContext + ) + } + + /** + * Holds if flow from `p` to a return node of kind `kind` is allowed. + * + * We don't expect a parameter to return stored in itself, unless + * explicitly allowed + */ + bindingset[p, kind] + private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { + exists(ParameterPosition pos | p.isParameterOf(_, pos) | + not kind.(ParamUpdateReturnKind).getPosition() = pos + or + allowParameterReturnInSelfCached(p.asNode()) + ) + } + + private module Stage1 implements StageSig { + class Ap extends int { + // workaround for bad functionality-induced joins (happens when using `Unit`) + pragma[nomagic] + Ap() { this in [0 .. 1] and this < 1 } + } + + private class Cc = boolean; + + /* Begin: Stage 1 logic. */ + /** + * Holds if `node` is reachable from a source. + * + * The Boolean `cc` records whether the node is reached through an + * argument in a call. + */ + private predicate fwdFlow(NodeEx node, Cc cc) { + sourceNode(node, _) and + if hasSourceCallCtx() then cc = true else cc = false + or + exists(NodeEx mid | fwdFlow(mid, cc) | + localFlowStepEx(mid, node) or + additionalLocalFlowStep(mid, node) or + additionalLocalStateStep(mid, _, node, _) ) or - exists(NodeEx mid | - fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa, pragma[only_bind_into](config)) and - jumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(mid, state, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(mid, state0, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStateStep(mid, state0, node, state, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) + exists(NodeEx mid | fwdFlow(mid, _) and cc = false | + jumpStepEx(mid, node) or + additionalJumpStep(mid, node) or + additionalJumpStateStep(mid, _, node, _) ) or // store - exists(TypedContent tc, Ap ap0 | - fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp, config) and - ap = apCons(tc, ap0) and - apa = getApprox(ap) + exists(NodeEx mid | + useFieldFlow() and + fwdFlow(mid, cc) and + storeEx(mid, _, node, _) ) or // read - exists(Ap ap0, Content c | - fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp, config) and - fwdFlowConsCand(ap0, c, ap, config) and - apa = getApprox(ap) + exists(ContentSet c | + fwdFlowReadSet(c, node, cc) and + fwdFlowConsCandSet(c, _) ) or // flow into a callable - fwdFlowIn(_, node, state, _, cc, _, _, ap, apa, config) and - if PrevStage::parameterMayFlowThrough(node, apa, config) - then ( - summaryCtx = TParamNodeSome(node.asNode()) and - argAp = apSome(ap) - ) else ( - summaryCtx = TParamNodeNone() and argAp = apNone() - ) + fwdFlowIn(_, _, _, node) and + cc = true or // flow out of a callable - exists( - DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, - DataFlowCallable inner - | - fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa, config) and - flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa, config) and - inner = ret.getEnclosingCallable() and - cc = getCallContextReturn(inner, call, innercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) + fwdFlowOut(_, node, false) and + cc = false or // flow through a callable - exists( - DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, ApApprox innerArgApa - | - fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowStore( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(DataFlowType contentType, ApApprox apa1 | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1, config) and - PrevStage::storeStepCand(node1, apa1, tc, node2, contentType, config) and - typecheckStore(ap1, contentType) - ) - } - - /** - * Holds if forward flow with access path `tail` reaches a store of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(TypedContent tc | - fwdFlowStore(_, tail, tc, _, _, _, _, _, config) and - tc.getContent() = c and - cons = apCons(tc, tail) - ) - } - - pragma[nomagic] - private predicate readStepCand( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - PrevStage::readStepCand(node1, c, node2, config) and - apc = projectToHeadContent(c) - } - - bindingset[node1, apc] - pragma[inline_late] - private predicate readStepCand0( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - readStepCand(node1, apc, c, node2, config) - } - - pragma[nomagic] - private predicate fwdFlowRead( - Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(ApHeadContent apc | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap, config) and - apc = getHeadContent(ap) and - readStepCand0(node1, apc, c, node2, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowIn( - DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, - ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa, Configuration config - ) { - exists(ArgNodeEx arg, boolean allowsFieldFlow | - fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa, config) and - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowRetFromArg( - RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, ApApprox argApa, - Ap ap, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - fwdFlow(pragma[only_bind_into](ret), state, ccc, - TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), - pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa), - pragma[only_bind_into](config)) and - kind = ret.getKind() and - parameterFlowThroughAllowed(summaryCtx, kind) and - argApa = getApprox(argAp) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) + exists(DataFlowCall call | + fwdFlowOutFromArg(call, node) and + fwdFlowIsEntered(call, cc) ) } + // inline to reduce the number of iterations pragma[inline] - private predicate fwdFlowThrough0( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, - Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa, config) and - fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp, config) - } - - pragma[nomagic] - private predicate fwdFlowThrough( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa, - config) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` - * and data might flow through the target callable and back out at `call`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered( - DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, - ParamNodeEx p, Ap ap, Configuration config - ) { - exists(ApApprox apa | - fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(p, apa, config) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate storeStepFwd( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config - ) { - fwdFlowStore(node1, ap1, tc, node2, _, _, _, _, config) and - ap2 = apCons(tc, ap1) and - fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _, config) - } - - private predicate readStepFwd( - NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config - ) { - fwdFlowRead(ap1, c, n1, n2, _, _, _, _, config) and - fwdFlowConsCand(ap1, c, ap2, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough0( - DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, - ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, - innerArgApa, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough( - RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, - Ap ap, Configuration config - ) { - exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | - returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa, config) and - pos = ret.getReturnPosition() and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowThroughIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap, - Configuration config - ) { - exists(ApApprox argApa | - flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), - allowsFieldFlow, argApa, pragma[only_bind_into](config)) and - fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa, - pragma[only_bind_into](config)) and - returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap, - pragma[only_bind_into](config)) and - if allowsFieldFlow = false then argAp instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowIntoCallAp( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap, - Configuration config - ) { - exists(ApApprox apa | - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - fwdFlow(arg, _, _, _, _, ap, apa, config) - ) - } - - pragma[nomagic] - private predicate flowOutOfCallAp( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, - Ap ap, Configuration config - ) { - exists(ApApprox apa | - flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa, config) and - fwdFlow(ret, _, _, _, _, ap, apa, config) and - pos = ret.getReturnPosition() - ) - } - - /** - * Holds if `node` with access path `ap` is part of a path from a source to a - * sink in the configuration `config`. - * - * The parameter `returnCtx` records whether (and how) the node must be returned - * from the enclosing callable in order to reach a sink, and if so, `returnAp` - * records the access path of the returned value. - */ - pragma[nomagic] - additional predicate revFlow( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - revFlow0(node, state, returnCtx, returnAp, ap, config) and - fwdFlow(node, state, _, _, _, ap, config) - } - - pragma[nomagic] - private predicate revFlow0( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, _, _, _, ap, config) and - sinkNode(node, state, config) and + private predicate fwdFlowIn(DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p) { + // call context cannot help reduce virtual dispatch + fwdFlow(arg, cc) and + viableParamArgEx(call, p, arg) and + not fullBarrier(p) and ( - if hasSinkCallCtx(config) - then returnCtx = TReturnCtxNoFlowThrough() - else returnCtx = TReturnCtxNone() - ) and - returnAp = apNone() and - ap instanceof ApNil - or - exists(NodeEx mid, FlowState state0 | - localStep(node, state, mid, state0, true, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, ap, config) + cc = false + or + cc = true and + not reducedViableImplInCallContext(call, _, _) ) or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap, pragma[only_bind_into](config)) and - localStep(node, pragma[only_bind_into](state), mid, state0, false, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, nil, pragma[only_bind_into](config)) and - ap instanceof ApNil + // call context may help reduce virtual dispatch + exists(DataFlowCallable target | + fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target) and + target = viableImplInSomeFwdFlowCallContextExt(call) and + cc = true ) - or + } + + /** + * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. + */ + pragma[nomagic] + private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc) { fwdFlowIn(call, _, cc, _) } + + pragma[nomagic] + private predicate fwdFlowInReducedViableImplInSomeCallContext( + DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target + ) { + fwdFlow(arg, true) and + viableParamArgEx(call, p, arg) and + reducedViableImplInCallContext(call, _, _) and + target = p.getEnclosingCallable() and + not fullBarrier(p) + } + + /** + * Gets a viable dispatch target of `call` in the context `ctx`. This is + * restricted to those `call`s for which a context might make a difference, + * and to `ctx`s that are reachable in `fwdFlow`. + */ + pragma[nomagic] + private DataFlowCallable viableImplInSomeFwdFlowCallContextExt(DataFlowCall call) { + exists(DataFlowCall ctx | + fwdFlowIsEntered(ctx, _) and + result = viableImplInCallContextExt(call, ctx) + ) + } + + private predicate fwdFlow(NodeEx node) { fwdFlow(node, _) } + + pragma[nomagic] + private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc) { exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, state, _, _, ap, config) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() + fwdFlow(mid, cc) and + readSetEx(mid, c, node) + ) + } + + /** + * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. + */ + pragma[assume_small_delta] + pragma[nomagic] + private predicate fwdFlowConsCand(Content c) { + exists(NodeEx mid, NodeEx node, TypedContent tc | + not fullBarrier(node) and + useFieldFlow() and + fwdFlow(mid, _) and + storeEx(mid, tc, node, _) and + c = tc.getContent() + ) + } + + /** + * Holds if `cs` may be interpreted in a read as the target of some store + * into `c`, in the flow covered by `fwdFlow`. + */ + pragma[nomagic] + private predicate fwdFlowConsCandSet(ContentSet cs, Content c) { + fwdFlowConsCand(c) and + c = cs.getAReadContent() + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc) { + exists(RetNodeEx ret | + fwdFlow(ret, cc) and + ret.getReturnPosition() = pos + ) + } + + // inline to reduce the number of iterations + pragma[inline] + private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc) and + viableReturnPosOutEx(call, pos, out) and + not fullBarrier(out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out) { + fwdFlowOut(call, out, true) + } + + private predicate stateStepFwd(FlowState state1, FlowState state2) { + exists(NodeEx node1 | + additionalLocalStateStep(node1, state1, _, state2) or + additionalJumpStateStep(node1, state1, _, state2) + | + fwdFlow(node1) + ) + } + + private predicate fwdFlowState(FlowState state) { + sourceNode(_, state) + or + exists(FlowState state0 | + fwdFlowState(state0) and + stateStepFwd(state0, state) + ) + } + + /** + * Holds if `node` is part of a path from a source to a sink. + * + * The Boolean `toReturn` records whether the node must be returned from + * the enclosing callable in order to reach a sink. + */ + pragma[nomagic] + private predicate revFlow(NodeEx node, boolean toReturn) { + revFlow0(node, toReturn) and + fwdFlow(node) + } + + pragma[nomagic] + private predicate revFlow0(NodeEx node, boolean toReturn) { + exists(FlowState state | + fwdFlow(node) and + sinkNode(node, state) and + fwdFlowState(state) and + if hasSinkCallCtx() then toReturn = true else toReturn = false ) or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStep(node, mid, config) and - revFlow(pragma[only_bind_into](mid), state, _, _, nil, pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil + exists(NodeEx mid | revFlow(mid, toReturn) | + localFlowStepEx(node, mid) or + additionalLocalFlowStep(node, mid) or + additionalLocalStateStep(node, _, mid, _) ) or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStateStep(node, state, mid, state0, config) and - revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil, - pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil + exists(NodeEx mid | revFlow(mid, _) and toReturn = false | + jumpStepEx(node, mid) or + additionalJumpStep(node, mid) or + additionalJumpStateStep(node, _, mid, _) ) or // store - exists(Ap ap0, Content c | - revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp, config) and - revFlowConsCand(ap0, c, ap, config) + exists(Content c | + revFlowStore(c, node, toReturn) and + revFlowConsCand(c) ) or // read - exists(NodeEx mid, Ap ap0 | - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - readStepFwd(node, ap, _, mid, ap0, config) + exists(NodeEx mid, ContentSet c | + readSetEx(node, c, mid) and + fwdFlowConsCandSet(c, _) and + revFlow(mid, toReturn) ) or // flow into a callable - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, state, TReturnCtxNone(), returnAp, ap, config) and - flowIntoCallAp(_, node, p, allowsFieldFlow, ap, config) and - (if allowsFieldFlow = false then ap instanceof ApNil else any()) and - returnCtx = TReturnCtxNone() - ) - or - // flow through a callable - exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, node, p, _, ap, innerReturnAp, config) - ) + revFlowIn(_, node, false) and + toReturn = false or // flow out of a callable exists(ReturnPosition pos | - revFlowOut(_, node, pos, state, _, _, ap, config) and - if returnFlowsThrough(node, pos, state, _, _, _, ap, config) - then ( - returnCtx = TReturnCtxMaybeFlowThrough(pos) and - returnAp = apSome(ap) - ) else ( - returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() - ) + revFlowOut(pos) and + node.(RetNodeEx).getReturnPosition() = pos and + toReturn = true + ) + or + // flow through a callable + exists(DataFlowCall call | + revFlowInToReturn(call, node) and + revFlowIsReturned(call, toReturn) ) - } - - pragma[nomagic] - private predicate revFlowStore( - Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, - ReturnCtx returnCtx, ApOption returnAp, Configuration config - ) { - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - storeStepFwd(node, ap, tc, mid, ap0, config) and - tc.getContent() = c } /** - * Holds if reverse flow with access path `tail` reaches a read of `c` - * resulting in access path `cons`. + * Holds if `c` is the target of a read in the flow covered by `revFlow`. */ pragma[nomagic] - private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(NodeEx mid, Ap tail0 | - revFlow(mid, _, _, _, tail, config) and - tail = pragma[only_bind_into](tail0) and - readStepFwd(_, cons, c, mid, tail0, config) + private predicate revFlowConsCand(Content c) { + exists(NodeEx mid, NodeEx node, ContentSet cs | + fwdFlow(node) and + readSetEx(node, cs, mid) and + fwdFlowConsCandSet(cs, c) and + revFlow(pragma[only_bind_into](mid), _) ) } pragma[nomagic] - private predicate revFlowOut( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, - ApOption returnAp, Ap ap, Configuration config + private predicate revFlowStore(Content c, NodeEx node, boolean toReturn) { + exists(NodeEx mid, TypedContent tc | + revFlow(mid, toReturn) and + fwdFlowConsCand(c) and + storeEx(node, tc, mid, _) and + c = tc.getContent() + ) + } + + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + pragma[nomagic] + additional predicate revFlowIsReadAndStored(Content c) { + revFlowConsCand(c) and + revFlowStore(c, _, _) + } + + pragma[nomagic] + additional predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, NodeEx out ) { - exists(NodeEx out, boolean allowsFieldFlow | - revFlow(out, state, returnCtx, returnAp, ap, config) and - flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() + fwdFlowReturnPosition(pos, _) and + viableReturnPosOutEx(call, pos, out) + } + + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos) { + exists(NodeEx out | + revFlow(out, _) and + viableReturnPosOutNodeCandFwd1(_, pos, out) ) } pragma[nomagic] - private predicate revFlowParamToReturn( - ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap, Configuration config - ) { - revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), - pragma[only_bind_into](ap), pragma[only_bind_into](config)) and - parameterFlowThroughAllowed(p, pos.getKind()) and - PrevStage::parameterMayFlowThrough(p, getApprox(ap), config) + additional predicate viableParamArgNodeCandFwd1(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { + fwdFlowIn(call, arg, _, p) + } + + // inline to reduce the number of iterations + pragma[inline] + private predicate revFlowIn(DataFlowCall call, ArgNodeEx arg, boolean toReturn) { + exists(ParamNodeEx p | + revFlow(p, toReturn) and + viableParamArgNodeCandFwd1(call, p, arg) + ) } pragma[nomagic] - private predicate revFlowThrough( - DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, - ApOption returnAp, Ap ap, Ap innerReturnAp, Configuration config - ) { - revFlowParamToReturn(p, state, pos, innerReturnAp, ap, config) and - revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp, config) + private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg) { + revFlowIn(call, arg, true) } /** @@ -1866,896 +766,1704 @@ private module MkStage { * reaching an argument of `call`. */ pragma[nomagic] - private predicate revFlowIsReturned( - DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap, - Configuration config - ) { - exists(RetNodeEx ret, FlowState state, CcCall ccc | - revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap, config) and - returnFlowsThrough(ret, pos, state, ccc, _, _, ap, config) and - matchesCall(ccc, call) + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn) { + exists(NodeEx out | + revFlow(out, toReturn) and + fwdFlowOutFromArg(call, out) + ) + } + + private predicate stateStepRev(FlowState state1, FlowState state2) { + exists(NodeEx node1, NodeEx node2 | + additionalLocalStateStep(node1, state1, node2, state2) or + additionalJumpStateStep(node1, state1, node2, state2) + | + revFlow(node1, _) and + revFlow(node2, _) and + fwdFlowState(state1) and + fwdFlowState(state2) + ) + } + + pragma[nomagic] + additional predicate revFlowState(FlowState state) { + exists(NodeEx node | + sinkNode(node, state) and + revFlow(node, _) and + fwdFlowState(state) + ) + or + exists(FlowState state0 | + revFlowState(state0) and + stateStepRev(state, state0) ) } pragma[nomagic] predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType ) { - exists(Ap ap2, Content c | - PrevStage::storeStepCand(node1, _, tc, node2, contentType, config) and - revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _, config) and - revFlowConsCand(ap2, c, ap1, config) - ) - } - - predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(Ap ap1, Ap ap2 | - revFlow(node2, _, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and - readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _, - pragma[only_bind_into](config)) - ) - } - - additional predicate revFlow(NodeEx node, FlowState state, Configuration config) { - revFlow(node, state, _, _, _, config) - } - - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, _, _, ap, config) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, _, _, _, ap, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, Configuration config) { - revFlow(node, _, _, _, _, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, ap, config) - } - - private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepFwd(_, ap, tc, _, _, config) - } - - private predicate revConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepCand(_, ap, tc, _, _, config) - } - - private predicate validAp(Ap ap, Configuration config) { - revFlow(_, _, _, _, ap, config) and ap instanceof ApNil - or - exists(TypedContent head, Ap tail | - consCand(head, tail, config) and - ap = apCons(head, tail) - ) - } - - additional predicate consCand(TypedContent tc, Ap ap, Configuration config) { - revConsCand(tc, ap, config) and - validAp(ap, config) - } - - pragma[nomagic] - private predicate parameterFlowsThroughRev( - ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp, Configuration config - ) { - revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap, config) and - parameterFlowThroughAllowed(p, pos.getKind()) - } - - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(ReturnPosition pos | - returnFlowsThrough(_, pos, _, _, p, ap, _, config) and - parameterFlowsThroughRev(p, ap, pos, _, config) + exists(Content c | + revFlowIsReadAndStored(c) and + revFlow(node2) and + storeEx(node1, tc, node2, contentType) and + c = tc.getContent() and + exists(ap1) ) } pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - exists(ParamNodeEx p, ReturnPosition pos | - returnFlowsThrough(ret, pos, _, _, p, argAp, ap, config) and - parameterFlowsThroughRev(p, argAp, pos, ap, config) and - kind = pos.getKind() + predicate readStepCand(NodeEx n1, Content c, NodeEx n2) { + revFlowIsReadAndStored(c) and + read(n1, c, n2) and + revFlow(n2) + } + + pragma[nomagic] + predicate revFlow(NodeEx node) { revFlow(node, _) } + + pragma[nomagic] + predicate revFlowAp(NodeEx node, Ap ap) { + revFlow(node) and + exists(ap) + } + + bindingset[node, state] + predicate revFlow(NodeEx node, FlowState state, Ap ap) { + revFlow(node, _) and + exists(state) and + exists(ap) + } + + private predicate throughFlowNodeCand(NodeEx node) { + revFlow(node, true) and + fwdFlow(node, true) and + not inBarrier(node) and + not outBarrier(node) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand(DataFlowCallable callable, ReturnKindExt kind) { + exists(RetNodeEx ret | + throughFlowNodeCand(ret) and + callable = ret.getEnclosingCallable() and + kind = ret.getKind() + ) + } + + /** + * Holds if flow may enter through `p` and reach a return node making `p` a + * candidate for the origin of a summary. + */ + pragma[nomagic] + predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap) { + exists(DataFlowCallable c, ReturnKindExt kind | + throughFlowNodeCand(p) and + returnFlowCallableNodeCand(c, kind) and + p.getEnclosingCallable() = c and + exists(ap) and + parameterFlowThroughAllowed(p, kind) ) } pragma[nomagic] - private predicate revFlowThroughArg( - DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, - Ap ap, Configuration config - ) { - exists(ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp, config) - ) + predicate returnMayFlowThrough(RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind) { + throughFlowNodeCand(ret) and + kind = ret.getKind() and + exists(argAp) and + exists(ap) } pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | - revFlow(arg, state, returnCtx, returnAp, ap, config) and - revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap, config) + predicate callMayFlowThroughRev(DataFlowCall call) { + exists(ArgNodeEx arg, boolean toReturn | + revFlow(arg, toReturn) and + revFlowInToReturn(call, arg) and + revFlowIsReturned(call, toReturn) ) } additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config + boolean fwd, int nodes, int fields, int conscand, int states, int tuples ) { fwd = true and - nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, config)) and - fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and - states = count(FlowState state | fwdFlow(_, state, _, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | - fwdFlow(n, state, cc, summaryCtx, argAp, ap, config) - ) + nodes = count(NodeEx node | fwdFlow(node)) and + fields = count(Content f0 | fwdFlowConsCand(f0)) and + conscand = -1 and + states = count(FlowState state | fwdFlowState(state)) and + tuples = count(NodeEx n, boolean b | fwdFlow(n, b)) or fwd = false and - nodes = count(NodeEx node | revFlow(node, _, _, _, _, config)) and - fields = count(TypedContent f0 | consCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and - states = count(FlowState state | revFlow(_, state, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | - revFlow(n, state, returnCtx, retAp, ap, config) - ) + nodes = count(NodeEx node | revFlow(node, _)) and + fields = count(Content f0 | revFlowConsCand(f0)) and + conscand = -1 and + states = count(FlowState state | revFlowState(state)) and + tuples = count(NodeEx n, boolean b | revFlow(n, b)) } - /* End: Stage logic. */ - } -} - -private module BooleanCallContext { - class Cc extends boolean { - Cc() { this in [true, false] } + /* End: Stage 1 logic. */ } - class CcCall extends Cc { - CcCall() { this = true } + pragma[noinline] + private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2) { + Stage1::revFlow(node2) and + localFlowStepEx(node1, node2) } - /** Holds if the call context may be `call`. */ - predicate matchesCall(CcCall cc, DataFlowCall call) { any() } - - class CcNoCall extends Cc { - CcNoCall() { this = false } + pragma[noinline] + private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2) { + Stage1::revFlow(node2) and + additionalLocalFlowStep(node1, node2) } - Cc ccNone() { result = false } + pragma[nomagic] + private predicate viableReturnPosOutNodeCand1(DataFlowCall call, ReturnPosition pos, NodeEx out) { + Stage1::revFlow(out) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out) + } - CcCall ccSomeCall() { result = true } + /** + * Holds if data can flow out of `call` from `ret` to `out`, either + * through a `ReturnNode` or through an argument that has been mutated, and + * that this step is part of a path from a source to a sink. + */ + pragma[nomagic] + private predicate flowOutOfCallNodeCand1( + DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out + ) { + exists(ReturnPosition pos | + viableReturnPosOutNodeCand1(call, pos, out) and + pos = ret.getReturnPosition() and + kind = pos.getKind() and + Stage1::revFlow(ret) and + not outBarrier(ret) and + not inBarrier(out) + ) + } - class LocalCc = Unit; + pragma[nomagic] + private predicate viableParamArgNodeCand1(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { + Stage1::viableParamArgNodeCandFwd1(call, p, arg) and + Stage1::revFlow(arg) + } - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } + /** + * Holds if data can flow into `call` and that this step is part of a + * path from a source to a sink. + */ + pragma[nomagic] + private predicate flowIntoCallNodeCand1(DataFlowCall call, ArgNodeEx arg, ParamNodeEx p) { + viableParamArgNodeCand1(call, p, arg) and + Stage1::revFlow(p) and + not outBarrier(arg) and + not inBarrier(p) + } - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + /** + * Gets the amount of forward branching on the origin of a cross-call path + * edge in the graph of paths between sources and sinks that ignores call + * contexts. + */ + pragma[nomagic] + private int branch(NodeEx n1) { + result = + strictcount(NodeEx n | flowOutOfCallNodeCand1(_, n1, _, n) or flowIntoCallNodeCand1(_, n1, n)) + } - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } -} + /** + * Gets the amount of backward branching on the target of a cross-call path + * edge in the graph of paths between sources and sinks that ignores call + * contexts. + */ + pragma[nomagic] + private int join(NodeEx n2) { + result = + strictcount(NodeEx n | flowOutOfCallNodeCand1(_, n, _, n2) or flowIntoCallNodeCand1(_, n, n2)) + } -private module Level1CallContext { - class Cc = CallContext; + /** + * Holds if data can flow out of `call` from `ret` to `out`, either + * through a `ReturnNode` or through an argument that has been mutated, and + * that this step is part of a path from a source to a sink. The + * `allowsFieldFlow` flag indicates whether the branching is within the limit + * specified by the configuration. + */ + pragma[nomagic] + private predicate flowOutOfCallNodeCand1( + DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow + ) { + flowOutOfCallNodeCand1(call, ret, kind, out) and + exists(int b, int j | + b = branch(ret) and + j = join(out) and + if b.minimum(j) <= Config::fieldFlowBranchLimit() + then allowsFieldFlow = true + else allowsFieldFlow = false + ) + } - class CcCall = CallContextCall; + /** + * Holds if data can flow into `call` and that this step is part of a + * path from a source to a sink. The `allowsFieldFlow` flag indicates whether + * the branching is within the limit specified by the configuration. + */ + pragma[nomagic] + private predicate flowIntoCallNodeCand1( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow + ) { + flowIntoCallNodeCand1(call, arg, p) and + exists(int b, int j | + b = branch(arg) and + j = join(p) and + if b.minimum(j) <= Config::fieldFlowBranchLimit() + then allowsFieldFlow = true + else allowsFieldFlow = false + ) + } - pragma[inline] - predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } + private signature module StageSig { + class Ap; - class CcNoCall = CallContextNoCall; + predicate revFlow(NodeEx node); - Cc ccNone() { result instanceof CallContextAny } + predicate revFlowAp(NodeEx node, Ap ap); - CcCall ccSomeCall() { result instanceof CallContextSomeCall } + bindingset[node, state] + predicate revFlow(NodeEx node, FlowState state, Ap ap); + + predicate callMayFlowThroughRev(DataFlowCall call); + + predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap); + + predicate returnMayFlowThrough(RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind); + + predicate storeStepCand( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType + ); + + predicate readStepCand(NodeEx n1, Content c, NodeEx n2); + } + + private module MkStage { + class ApApprox = PrevStage::Ap; + + signature module StageParam { + class Ap; + + class ApNil extends Ap; + + bindingset[result, ap] + ApApprox getApprox(Ap ap); + + ApNil getApNil(NodeEx node); + + bindingset[tc, tail] + Ap apCons(TypedContent tc, Ap tail); + + /** + * An approximation of `Content` that corresponds to the precision level of + * `Ap`, such that the mappings from both `Ap` and `Content` to this type + * are functional. + */ + class ApHeadContent; + + ApHeadContent getHeadContent(Ap ap); + + ApHeadContent projectToHeadContent(Content c); + + class ApOption; + + ApOption apNone(); + + ApOption apSome(Ap ap); + + class Cc; + + class CcCall extends Cc; + + // TODO: member predicate on CcCall + predicate matchesCall(CcCall cc, DataFlowCall call); + + class CcNoCall extends Cc; + + Cc ccNone(); + + CcCall ccSomeCall(); + + class LocalCc; + + bindingset[call, c, outercc] + CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); + + bindingset[call, c, innercc] + CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); + + bindingset[node, cc] + LocalCc getLocalCc(NodeEx node, Cc cc); + + bindingset[node1, state1] + bindingset[node2, state2] + predicate localStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + ApNil ap, LocalCc lcc + ); + + predicate flowOutOfCall( + DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow + ); + + predicate flowIntoCall( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow + ); + + bindingset[node, state, ap] + predicate filter(NodeEx node, FlowState state, Ap ap); + + bindingset[ap, contentType] + predicate typecheckStore(Ap ap, DataFlowType contentType); + } + + module Stage implements StageSig { + import Param + + /* Begin: Stage logic. */ + // use an alias as a workaround for bad functionality-induced joins + pragma[nomagic] + private predicate revFlowApAlias(NodeEx node, ApApprox apa) { + PrevStage::revFlowAp(node, apa) + } + + pragma[nomagic] + private predicate flowIntoCallApa( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa + ) { + flowIntoCall(call, arg, p, allowsFieldFlow) and + PrevStage::revFlowAp(p, pragma[only_bind_into](apa)) and + revFlowApAlias(arg, pragma[only_bind_into](apa)) + } + + pragma[nomagic] + private predicate flowOutOfCallApa( + DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, + ApApprox apa + ) { + flowOutOfCall(call, ret, kind, out, allowsFieldFlow) and + PrevStage::revFlowAp(out, pragma[only_bind_into](apa)) and + revFlowApAlias(ret, pragma[only_bind_into](apa)) + } + + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, + ApApprox argApa, ApApprox apa + ) { + exists(ReturnKindExt kind | + flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa) and + PrevStage::callMayFlowThroughRev(call) and + PrevStage::returnMayFlowThrough(ret, argApa, apa, kind) and + matchesCall(ccc, call) + ) + } + + /** + * Holds if `node` is reachable with access path `ap` from a source. + * + * The call context `cc` records whether the node is reached through an + * argument in a call, and if so, `summaryCtx` and `argAp` record the + * corresponding parameter position and access path of that argument, respectively. + */ + pragma[nomagic] + additional predicate fwdFlow( + NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, + ApApprox apa + ) { + fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa) and + PrevStage::revFlow(node, state, apa) and + filter(node, state, ap) + } + + pragma[inline] + additional predicate fwdFlow( + NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap + ) { + fwdFlow(node, state, cc, summaryCtx, argAp, ap, _) + } + + pragma[assume_small_delta] + pragma[nomagic] + private predicate fwdFlow0( + NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, + ApApprox apa + ) { + sourceNode(node, state) and + (if hasSourceCallCtx() then cc = ccSomeCall() else cc = ccNone()) and + argAp = apNone() and + summaryCtx = TParamNodeNone() and + ap = getApNil(node) and + apa = getApprox(ap) + or + exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | + fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0) and + localCc = getLocalCc(mid, cc) + | + localStep(mid, state0, node, state, true, _, localCc) and + ap = ap0 and + apa = apa0 + or + localStep(mid, state0, node, state, false, ap, localCc) and + ap0 instanceof ApNil and + apa = getApprox(ap) + ) + or + exists(NodeEx mid | + fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa) and + jumpStepEx(mid, node) and + cc = ccNone() and + summaryCtx = TParamNodeNone() and + argAp = apNone() + ) + or + exists(NodeEx mid, ApNil nil | + fwdFlow(mid, state, _, _, _, nil) and + additionalJumpStep(mid, node) and + cc = ccNone() and + summaryCtx = TParamNodeNone() and + argAp = apNone() and + ap = getApNil(node) and + apa = getApprox(ap) + ) + or + exists(NodeEx mid, FlowState state0, ApNil nil | + fwdFlow(mid, state0, _, _, _, nil) and + additionalJumpStateStep(mid, state0, node, state) and + cc = ccNone() and + summaryCtx = TParamNodeNone() and + argAp = apNone() and + ap = getApNil(node) and + apa = getApprox(ap) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp) and + ap = apCons(tc, ap0) and + apa = getApprox(ap) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp) and + fwdFlowConsCand(ap0, c, ap) and + apa = getApprox(ap) + ) + or + // flow into a callable + fwdFlowIn(_, node, state, _, cc, _, _, ap, apa) and + if PrevStage::parameterMayFlowThrough(node, apa) + then ( + summaryCtx = TParamNodeSome(node.asNode()) and + argAp = apSome(ap) + ) else ( + summaryCtx = TParamNodeNone() and argAp = apNone() + ) + or + // flow out of a callable + exists( + DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | + fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa) and + flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa) and + inner = ret.getEnclosingCallable() and + cc = getCallContextReturn(inner, call, innercc) and + if allowsFieldFlow = false then ap instanceof ApNil else any() + ) + or + // flow through a callable + exists( + DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, + ApApprox innerArgApa + | + fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa) and + flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa) and + if allowsFieldFlow = false then ap instanceof ApNil else any() + ) + } + + pragma[nomagic] + private predicate fwdFlowStore( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, + ParamNodeOption summaryCtx, ApOption argAp + ) { + exists(DataFlowType contentType, ApApprox apa1 | + fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1) and + PrevStage::storeStepCand(node1, apa1, tc, node2, contentType) and + typecheckStore(ap1, contentType) + ) + } + + /** + * Holds if forward flow with access path `tail` reaches a store of `c` + * resulting in access path `cons`. + */ + pragma[nomagic] + private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail) { + exists(TypedContent tc | + fwdFlowStore(_, tail, tc, _, _, _, _, _) and + tc.getContent() = c and + cons = apCons(tc, tail) + ) + } + + pragma[nomagic] + private predicate readStepCand(NodeEx node1, ApHeadContent apc, Content c, NodeEx node2) { + PrevStage::readStepCand(node1, c, node2) and + apc = projectToHeadContent(c) + } + + bindingset[node1, apc] + pragma[inline_late] + private predicate readStepCand0(NodeEx node1, ApHeadContent apc, Content c, NodeEx node2) { + readStepCand(node1, apc, c, node2) + } + + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, + ParamNodeOption summaryCtx, ApOption argAp + ) { + exists(ApHeadContent apc | + fwdFlow(node1, state, cc, summaryCtx, argAp, ap) and + apc = getHeadContent(ap) and + readStepCand0(node1, apc, c, node2) + ) + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, + ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa + ) { + exists(ArgNodeEx arg, boolean allowsFieldFlow | + fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa) and + flowIntoCallApa(call, arg, p, allowsFieldFlow, apa) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and + if allowsFieldFlow = false then ap instanceof ApNil else any() + ) + } + + pragma[nomagic] + private predicate fwdFlowRetFromArg( + RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, + ApApprox argApa, Ap ap, ApApprox apa + ) { + exists(ReturnKindExt kind | + fwdFlow(pragma[only_bind_into](ret), state, ccc, + TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), + pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa)) and + kind = ret.getKind() and + parameterFlowThroughAllowed(summaryCtx, kind) and + argApa = getApprox(argAp) and + PrevStage::returnMayFlowThrough(ret, argApa, apa, kind) + ) + } + + pragma[inline] + private predicate fwdFlowThrough0( + DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, + ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, + Ap innerArgAp, ApApprox innerArgApa + ) { + fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa) and + fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp) + } + + pragma[nomagic] + private predicate fwdFlowThrough( + DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, + ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa + ) { + fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa) + } + + /** + * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` + * and data might flow through the target callable and back out at `call`. + */ + pragma[nomagic] + private predicate fwdFlowIsEntered( + DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, + ParamNodeEx p, Ap ap + ) { + exists(ApApprox apa | + fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, + pragma[only_bind_into](apa)) and + PrevStage::parameterMayFlowThrough(p, apa) and + PrevStage::callMayFlowThroughRev(call) + ) + } + + pragma[nomagic] + private predicate storeStepFwd(NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2) { + fwdFlowStore(node1, ap1, tc, node2, _, _, _, _) and + ap2 = apCons(tc, ap1) and + fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _) + } + + private predicate readStepFwd(NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2) { + fwdFlowRead(ap1, c, n1, n2, _, _, _, _) and + fwdFlowConsCand(ap1, c, ap2) + } + + pragma[nomagic] + private predicate returnFlowsThrough0( + DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, + ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa + ) { + fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, + innerArgApa) + } + + pragma[nomagic] + private predicate returnFlowsThrough( + RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, + Ap ap + ) { + exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | + returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa) and + flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa) and + pos = ret.getReturnPosition() and + if allowsFieldFlow = false then ap instanceof ApNil else any() + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap + ) { + exists(ApApprox argApa | + flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), + allowsFieldFlow, argApa) and + fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa) and + returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap) and + if allowsFieldFlow = false then argAp instanceof ApNil else any() + ) + } + + pragma[nomagic] + private predicate flowIntoCallAp( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap + ) { + exists(ApApprox apa | + flowIntoCallApa(call, arg, p, allowsFieldFlow, apa) and + fwdFlow(arg, _, _, _, _, ap, apa) + ) + } + + pragma[nomagic] + private predicate flowOutOfCallAp( + DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, + Ap ap + ) { + exists(ApApprox apa | + flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa) and + fwdFlow(ret, _, _, _, _, ap, apa) and + pos = ret.getReturnPosition() + ) + } + + /** + * Holds if `node` with access path `ap` is part of a path from a source to a + * sink. + * + * The parameter `returnCtx` records whether (and how) the node must be returned + * from the enclosing callable in order to reach a sink, and if so, `returnAp` + * records the access path of the returned value. + */ + pragma[nomagic] + additional predicate revFlow( + NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap + ) { + revFlow0(node, state, returnCtx, returnAp, ap) and + fwdFlow(node, state, _, _, _, ap) + } + + pragma[nomagic] + private predicate revFlow0( + NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap + ) { + fwdFlow(node, state, _, _, _, ap) and + sinkNode(node, state) and + ( + if hasSinkCallCtx() + then returnCtx = TReturnCtxNoFlowThrough() + else returnCtx = TReturnCtxNone() + ) and + returnAp = apNone() and + ap instanceof ApNil + or + exists(NodeEx mid, FlowState state0 | + localStep(node, state, mid, state0, true, _, _) and + revFlow(mid, state0, returnCtx, returnAp, ap) + ) + or + exists(NodeEx mid, FlowState state0, ApNil nil | + fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap) and + localStep(node, pragma[only_bind_into](state), mid, state0, false, _, _) and + revFlow(mid, state0, returnCtx, returnAp, nil) and + ap instanceof ApNil + ) + or + exists(NodeEx mid | + jumpStepEx(node, mid) and + revFlow(mid, state, _, _, ap) and + returnCtx = TReturnCtxNone() and + returnAp = apNone() + ) + or + exists(NodeEx mid, ApNil nil | + fwdFlow(node, _, _, _, _, ap) and + additionalJumpStep(node, mid) and + revFlow(pragma[only_bind_into](mid), state, _, _, nil) and + returnCtx = TReturnCtxNone() and + returnAp = apNone() and + ap instanceof ApNil + ) + or + exists(NodeEx mid, FlowState state0, ApNil nil | + fwdFlow(node, _, _, _, _, ap) and + additionalJumpStateStep(node, state, mid, state0) and + revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil) and + returnCtx = TReturnCtxNone() and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp) and + revFlowConsCand(ap0, c, ap) + ) + or + // read + exists(NodeEx mid, Ap ap0 | + revFlow(mid, state, returnCtx, returnAp, ap0) and + readStepFwd(node, ap, _, mid, ap0) + ) + or + // flow into a callable + exists(ParamNodeEx p, boolean allowsFieldFlow | + revFlow(p, state, TReturnCtxNone(), returnAp, ap) and + flowIntoCallAp(_, node, p, allowsFieldFlow, ap) and + (if allowsFieldFlow = false then ap instanceof ApNil else any()) and + returnCtx = TReturnCtxNone() + ) + or + // flow through a callable + exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | + revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp) and + flowThroughIntoCall(call, node, p, _, ap, innerReturnAp) + ) + or + // flow out of a callable + exists(ReturnPosition pos | + revFlowOut(_, node, pos, state, _, _, ap) and + if returnFlowsThrough(node, pos, state, _, _, _, ap) + then ( + returnCtx = TReturnCtxMaybeFlowThrough(pos) and + returnAp = apSome(ap) + ) else ( + returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() + ) + ) + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, + ReturnCtx returnCtx, ApOption returnAp + ) { + revFlow(mid, state, returnCtx, returnAp, ap0) and + storeStepFwd(node, ap, tc, mid, ap0) and + tc.getContent() = c + } + + /** + * Holds if reverse flow with access path `tail` reaches a read of `c` + * resulting in access path `cons`. + */ + pragma[nomagic] + private predicate revFlowConsCand(Ap cons, Content c, Ap tail) { + exists(NodeEx mid, Ap tail0 | + revFlow(mid, _, _, _, tail) and + tail = pragma[only_bind_into](tail0) and + readStepFwd(_, cons, c, mid, tail0) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, + ApOption returnAp, Ap ap + ) { + exists(NodeEx out, boolean allowsFieldFlow | + revFlow(out, state, returnCtx, returnAp, ap) and + flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap) and + if allowsFieldFlow = false then ap instanceof ApNil else any() + ) + } + + pragma[nomagic] + private predicate revFlowParamToReturn( + ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap + ) { + revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), + pragma[only_bind_into](ap)) and + parameterFlowThroughAllowed(p, pos.getKind()) and + PrevStage::parameterMayFlowThrough(p, getApprox(ap)) + } + + pragma[nomagic] + private predicate revFlowThrough( + DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, + ApOption returnAp, Ap ap, Ap innerReturnAp + ) { + revFlowParamToReturn(p, state, pos, innerReturnAp, ap) and + revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. + */ + pragma[nomagic] + private predicate revFlowIsReturned( + DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap + ) { + exists(RetNodeEx ret, FlowState state, CcCall ccc | + revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap) and + returnFlowsThrough(ret, pos, state, ccc, _, _, ap) and + matchesCall(ccc, call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType + ) { + exists(Ap ap2, Content c | + PrevStage::storeStepCand(node1, _, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _) and + revFlowConsCand(ap2, c, ap1) + ) + } + + predicate readStepCand(NodeEx node1, Content c, NodeEx node2) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, _, pragma[only_bind_into](ap2)) and + readStepFwd(node1, ap1, c, node2, ap2) and + revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _) + ) + } + + additional predicate revFlow(NodeEx node, FlowState state) { revFlow(node, state, _, _, _) } + + predicate revFlow(NodeEx node, FlowState state, Ap ap) { revFlow(node, state, _, _, ap) } + + pragma[nomagic] + predicate revFlow(NodeEx node) { revFlow(node, _, _, _, _) } + + pragma[nomagic] + predicate revFlowAp(NodeEx node, Ap ap) { revFlow(node, _, _, _, ap) } + + // use an alias as a workaround for bad functionality-induced joins + pragma[nomagic] + additional predicate revFlowAlias(NodeEx node) { revFlow(node, _, _, _, _) } + + // use an alias as a workaround for bad functionality-induced joins + pragma[nomagic] + additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap) { + revFlow(node, state, ap) + } + + private predicate fwdConsCand(TypedContent tc, Ap ap) { storeStepFwd(_, ap, tc, _, _) } + + private predicate revConsCand(TypedContent tc, Ap ap) { storeStepCand(_, ap, tc, _, _) } + + private predicate validAp(Ap ap) { + revFlow(_, _, _, _, ap) and ap instanceof ApNil + or + exists(TypedContent head, Ap tail | + consCand(head, tail) and + ap = apCons(head, tail) + ) + } + + additional predicate consCand(TypedContent tc, Ap ap) { + revConsCand(tc, ap) and + validAp(ap) + } + + pragma[nomagic] + private predicate parameterFlowsThroughRev( + ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp + ) { + revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap) and + parameterFlowThroughAllowed(p, pos.getKind()) + } + + pragma[nomagic] + predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap) { + exists(ReturnPosition pos | + returnFlowsThrough(_, pos, _, _, p, ap, _) and + parameterFlowsThroughRev(p, ap, pos, _) + ) + } + + pragma[nomagic] + predicate returnMayFlowThrough(RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind) { + exists(ParamNodeEx p, ReturnPosition pos | + returnFlowsThrough(ret, pos, _, _, p, argAp, ap) and + parameterFlowsThroughRev(p, argAp, pos, ap) and + kind = pos.getKind() + ) + } + + pragma[nomagic] + private predicate revFlowThroughArg( + DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, + Ap ap + ) { + exists(ParamNodeEx p, Ap innerReturnAp | + revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp) and + flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp) + ) + } + + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call) { + exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | + revFlow(arg, state, returnCtx, returnAp, ap) and + revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap) + ) + } + + additional predicate stats( + boolean fwd, int nodes, int fields, int conscand, int states, int tuples + ) { + fwd = true and + nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap)) and + states = count(FlowState state | fwdFlow(_, state, _, _, _, _)) and + tuples = + count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | + fwdFlow(n, state, cc, summaryCtx, argAp, ap) + ) + or + fwd = false and + nodes = count(NodeEx node | revFlow(node, _, _, _, _)) and + fields = count(TypedContent f0 | consCand(f0, _)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap)) and + states = count(FlowState state | revFlow(_, state, _, _, _)) and + tuples = + count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | + revFlow(n, state, returnCtx, retAp, ap) + ) + } + /* End: Stage logic. */ + } + } + + private module BooleanCallContext { + class Cc extends boolean { + Cc() { this in [true, false] } + } + + class CcCall extends Cc { + CcCall() { this = true } + } + + /** Holds if the call context may be `call`. */ + predicate matchesCall(CcCall cc, DataFlowCall call) { any() } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccNone() { result = false } + + CcCall ccSomeCall() { result = true } - module NoLocalCallContext { class LocalCc = Unit; bindingset[node, cc] LocalCc getLocalCc(NodeEx node, Cc cc) { any() } bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSiteDispatch(call, c) - then result = TSpecificCall(call) - else result = TSomeCall() + CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c, innercc] + CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } + } + + private module Level1CallContext { + class Cc = CallContext; + + class CcCall = CallContextCall; + + pragma[inline] + predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } + + class CcNoCall = CallContextNoCall; + + Cc ccNone() { result instanceof CallContextAny } + + CcCall ccSomeCall() { result instanceof CallContextSomeCall } + + module NoLocalCallContext { + class LocalCc = Unit; + + bindingset[node, cc] + LocalCc getLocalCc(NodeEx node, Cc cc) { any() } + + bindingset[call, c, outercc] + CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + checkCallContextCall(outercc, call, c) and + if recordDataFlowCallSiteDispatch(call, c) + then result = TSpecificCall(call) + else result = TSomeCall() + } + } + + module LocalCallContext { + class LocalCc = LocalCallContext; + + bindingset[node, cc] + LocalCc getLocalCc(NodeEx node, Cc cc) { + result = + getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), + node.getEnclosingCallable()) + } + + bindingset[call, c, outercc] + CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + checkCallContextCall(outercc, call, c) and + if recordDataFlowCallSite(call, c) + then result = TSpecificCall(call) + else result = TSomeCall() + } + } + + bindingset[call, c, innercc] + CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { + checkCallContextReturn(innercc, c, call) and + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() } } - module LocalCallContext { - class LocalCc = LocalCallContext; + private module Stage2Param implements MkStage::StageParam { + private module PrevStage = Stage1; - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { - result = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - node.getEnclosingCallable()) + class Ap extends boolean { + Ap() { this in [true, false] } } - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + class ApNil extends Ap { + ApNil() { this = false } } + + bindingset[result, ap] + PrevStage::Ap getApprox(Ap ap) { any() } + + ApNil getApNil(NodeEx node) { Stage1::revFlow(node) and exists(result) } + + bindingset[tc, tail] + Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + class ApHeadContent = Unit; + + pragma[inline] + ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } + + ApHeadContent projectToHeadContent(Content c) { any() } + + class ApOption = BooleanOption; + + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + + import Level1CallContext + import NoLocalCallContext + + bindingset[node1, state1] + bindingset[node2, state2] + predicate localStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + ApNil ap, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2) and + state1 = state2 + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2) and + state1 = state2 + or + preservesValue = false and + additionalLocalStateStep(node1, state1, node2, state2) + ) and + exists(ap) and + exists(lcc) + } + + predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + predicate flowIntoCall = flowIntoCallNodeCand1/4; + + pragma[nomagic] + private predicate expectsContentCand(NodeEx node) { + exists(Content c | + PrevStage::revFlow(node) and + PrevStage::revFlowIsReadAndStored(c) and + expectsContentEx(node, c) + ) + } + + bindingset[node, state, ap] + predicate filter(NodeEx node, FlowState state, Ap ap) { + PrevStage::revFlowState(state) and + exists(ap) and + not stateBarrier(node, state) and + ( + notExpectsContent(node) + or + ap = true and + expectsContentCand(node) + ) + } + + bindingset[ap, contentType] + predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } } - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { - checkCallContextReturn(innercc, c, call) and - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() + private module Stage2 implements StageSig { + import MkStage::Stage } -} - -private module Stage2Param implements MkStage::StageParam { - private module PrevStage = Stage1; - - class Ap extends boolean { - Ap() { this in [true, false] } - } - - class ApNil extends Ap { - ApNil() { this = false } - } - - bindingset[result, ap] - PrevStage::Ap getApprox(Ap ap) { any() } - - ApNil getApNil(NodeEx node) { Stage1::revFlow(node, _) and exists(result) } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } - - class ApHeadContent = Unit; - - pragma[inline] - ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } - - ApHeadContent projectToHeadContent(Content c) { any() } - - class ApOption = BooleanOption; - - ApOption apNone() { result = TBooleanNone() } - - ApOption apSome(Ap ap) { result = TBooleanSome(ap) } - - import Level1CallContext - import NoLocalCallContext - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - ( - preservesValue = true and - localFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalStateStep(node1, state1, node2, state2, config) - ) and - exists(ap) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand1/6; - - predicate flowIntoCall = flowIntoCallNodeCand1/5; pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - expectsContentEx(node, c) - ) + private predicate flowOutOfCallNodeCand2( + DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow + ) { + flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow) and + Stage2::revFlow(node2) and + Stage2::revFlowAlias(node1) } - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - PrevStage::revFlowState(state, pragma[only_bind_into](config)) and - exists(ap) and - not stateBarrier(node, state, config) and - ( - notExpectsContent(node) - or - ap = true and - expectsContentCand(node, config) - ) + pragma[nomagic] + private predicate flowIntoCallNodeCand2( + DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow + ) { + flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow) and + Stage2::revFlow(node2) and + Stage2::revFlowAlias(node1) } - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage2 implements StageSig { - import MkStage::Stage -} - -pragma[nomagic] -private predicate flowOutOfCallNodeCand2( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -pragma[nomagic] -private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -private module LocalFlowBigStep { - /** - * A node where some checking is required, and hence the big-step relation - * is not allowed to step over. - */ - private class FlowCheckNode extends NodeEx { - FlowCheckNode() { - castNode(this.asNode()) or - clearsContentCached(this.asNode(), _) or - expectsContentCached(this.asNode(), _) + private module LocalFlowBigStep { + /** + * A node where some checking is required, and hence the big-step relation + * is not allowed to step over. + */ + private class FlowCheckNode extends NodeEx { + FlowCheckNode() { + castNode(this.asNode()) or + clearsContentCached(this.asNode(), _) or + expectsContentCached(this.asNode(), _) + } } - } - /** - * Holds if `node` can be the first node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowEntry(NodeEx node, FlowState state, Configuration config) { - Stage2::revFlow(node, state, config) and - ( - sourceNode(node, state, config) + /** + * Holds if `node` can be the first node in a maximal subsequence of local + * flow steps in a dataflow path. + */ + private predicate localFlowEntry(NodeEx node, FlowState state) { + Stage2::revFlow(node, state) and + ( + sourceNode(node, state) + or + jumpStepEx(_, node) + or + additionalJumpStep(_, node) + or + additionalJumpStateStep(_, _, node, state) + or + node instanceof ParamNodeEx + or + node.asNode() instanceof OutNodeExt + or + Stage2::storeStepCand(_, _, _, node, _) + or + Stage2::readStepCand(_, _, node) + or + node instanceof FlowCheckNode + or + exists(FlowState s | + additionalLocalStateStep(_, s, node, state) and + s != state + ) + ) + } + + /** + * Holds if `node` can be the last node in a maximal subsequence of local + * flow steps in a dataflow path. + */ + private predicate localFlowExit(NodeEx node, FlowState state) { + exists(NodeEx next | Stage2::revFlow(next, state) | + jumpStepEx(node, next) or + additionalJumpStep(node, next) or + flowIntoCallNodeCand2(_, node, next, _) or + flowOutOfCallNodeCand2(_, node, _, next, _) or + Stage2::storeStepCand(node, _, _, next, _) or + Stage2::readStepCand(node, _, next) + ) or - jumpStep(_, node, config) - or - additionalJumpStep(_, node, config) - or - additionalJumpStateStep(_, _, node, state, config) - or - node instanceof ParamNodeEx - or - node.asNode() instanceof OutNodeExt - or - Stage2::storeStepCand(_, _, _, node, _, config) - or - Stage2::readStepCand(_, _, node, config) - or - node instanceof FlowCheckNode - or - exists(FlowState s | - additionalLocalStateStep(_, s, node, state, config) and + exists(NodeEx next, FlowState s | Stage2::revFlow(next, s) | + additionalJumpStateStep(node, state, next, s) + or + additionalLocalStateStep(node, state, next, s) and s != state ) - ) - } - - /** - * Holds if `node` can be the last node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowExit(NodeEx node, FlowState state, Configuration config) { - exists(NodeEx next | Stage2::revFlow(next, state, config) | - jumpStep(node, next, config) or - additionalJumpStep(node, next, config) or - flowIntoCallNodeCand2(_, node, next, _, config) or - flowOutOfCallNodeCand2(_, node, _, next, _, config) or - Stage2::storeStepCand(node, _, _, next, _, config) or - Stage2::readStepCand(node, _, next, config) - ) - or - exists(NodeEx next, FlowState s | Stage2::revFlow(next, s, config) | - additionalJumpStateStep(node, state, next, s, config) or - additionalLocalStateStep(node, state, next, s, config) and - s != state - ) - or - Stage2::revFlow(node, state, config) and - node instanceof FlowCheckNode - or - sinkNode(node, state, config) - } + Stage2::revFlow(node, state) and + node instanceof FlowCheckNode + or + sinkNode(node, state) + } - pragma[noinline] - private predicate additionalLocalFlowStepNodeCand2( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, Configuration config - ) { - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 and - Stage2::revFlow(node1, pragma[only_bind_into](state1), false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false, - pragma[only_bind_into](config)) - or - additionalLocalStateStep(node1, state1, node2, state2, config) and - Stage2::revFlow(node1, state1, false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, state2, false, pragma[only_bind_into](config)) - } + pragma[noinline] + private predicate additionalLocalFlowStepNodeCand2( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2 + ) { + additionalLocalFlowStepNodeCand1(node1, node2) and + state1 = state2 and + Stage2::revFlow(node1, pragma[only_bind_into](state1), false) and + Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false) + or + additionalLocalStateStep(node1, state1, node2, state2) and + Stage2::revFlow(node1, state1, false) and + Stage2::revFlowAlias(node2, state2, false) + } - /** - * Holds if the local path from `node1` to `node2` is a prefix of a maximal - * subsequence of local flow steps in a dataflow path. - * - * This is the transitive closure of `[additional]localFlowStep` beginning - * at `localFlowEntry`. - */ - pragma[nomagic] - private predicate localFlowStepPlus( - NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, - Configuration config, LocalCallContext cc - ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and - ( - localFlowEntry(node1, pragma[only_bind_into](state), pragma[only_bind_into](config)) and + /** + * Holds if the local path from `node1` to `node2` is a prefix of a maximal + * subsequence of local flow steps in a dataflow path. + * + * This is the transitive closure of `[additional]localFlowStep` beginning + * at `localFlowEntry`. + */ + pragma[nomagic] + private predicate localFlowStepPlus( + NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, + LocalCallContext cc + ) { + not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and ( - localFlowStepNodeCand1(node1, node2, config) and - preservesValue = true and - t = node1.getDataFlowType() and // irrelevant dummy value - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) + localFlowEntry(node1, pragma[only_bind_into](state)) and + ( + localFlowStepNodeCand1(node1, node2) and + preservesValue = true and + t = node1.getDataFlowType() and // irrelevant dummy value + Stage2::revFlow(node2, pragma[only_bind_into](state)) + or + additionalLocalFlowStepNodeCand2(node1, state, node2, state) and + preservesValue = false and + t = node2.getDataFlowType() + ) and + node1 != node2 and + cc.relevantFor(node1.getEnclosingCallable()) and + not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) or - additionalLocalFlowStepNodeCand2(node1, state, node2, state, config) and - preservesValue = false and - t = node2.getDataFlowType() - ) and - node1 != node2 and - cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) - or - exists(NodeEx mid | - localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, - pragma[only_bind_into](config), cc) and - localFlowStepNodeCand1(mid, node2, config) and - not mid instanceof FlowCheckNode and - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) + exists(NodeEx mid | + localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, cc) and + localFlowStepNodeCand1(mid, node2) and + not mid instanceof FlowCheckNode and + Stage2::revFlow(node2, pragma[only_bind_into](state)) + ) + or + exists(NodeEx mid | + localFlowStepPlus(node1, state, mid, _, _, cc) and + additionalLocalFlowStepNodeCand2(mid, state, node2, state) and + not mid instanceof FlowCheckNode and + preservesValue = false and + t = node2.getDataFlowType() + ) ) + } + + /** + * Holds if `node1` can step to `node2` in one or more local steps and this + * path can occur as a maximal subsequence of local steps in a dataflow path. + */ + pragma[nomagic] + predicate localFlowBigStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + DataFlowType t, LocalCallContext callContext + ) { + localFlowStepPlus(node1, state1, node2, preservesValue, t, callContext) and + localFlowExit(node2, state1) and + state1 = state2 or - exists(NodeEx mid | - localFlowStepPlus(node1, state, mid, _, _, pragma[only_bind_into](config), cc) and - additionalLocalFlowStepNodeCand2(mid, state, node2, state, config) and - not mid instanceof FlowCheckNode and - preservesValue = false and - t = node2.getDataFlowType() + additionalLocalFlowStepNodeCand2(node1, state1, node2, state2) and + state1 != state2 and + preservesValue = false and + t = node2.getDataFlowType() and + callContext.relevantFor(node1.getEnclosingCallable()) and + not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | + isUnreachableInCallCached(node1.asNode(), call) or + isUnreachableInCallCached(node2.asNode(), call) ) + } + } + + private import LocalFlowBigStep + + private module Stage3Param implements MkStage::StageParam { + private module PrevStage = Stage2; + + class Ap = ApproxAccessPathFront; + + class ApNil = ApproxAccessPathFrontNil; + + PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + ApNil getApNil(NodeEx node) { + PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) + } + + bindingset[tc, tail] + Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } + + class ApHeadContent = ContentApprox; + + pragma[noinline] + ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + predicate projectToHeadContent = getContentApprox/1; + + class ApOption = ApproxAccessPathFrontOption; + + ApOption apNone() { result = TApproxAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } + + import BooleanCallContext + + predicate localStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + ApproxAccessPathFrontNil ap, LocalCc lcc + ) { + localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), _) and + exists(lcc) + } + + predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + predicate flowIntoCall = flowIntoCallNodeCand2/4; + + pragma[nomagic] + private predicate expectsContentCand(NodeEx node, Ap ap) { + exists(Content c | + PrevStage::revFlow(node) and + PrevStage::readStepCand(_, c, _) and + expectsContentEx(node, c) and + c = ap.getAHead().getContent() + ) + } + + pragma[nomagic] + private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } + + bindingset[node, state, ap] + predicate filter(NodeEx node, FlowState state, Ap ap) { + exists(state) and + (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and + ( + notExpectsContent(node) + or + expectsContentCand(node, ap) + ) + } + + bindingset[ap, contentType] + predicate typecheckStore(Ap ap, DataFlowType contentType) { + // We need to typecheck stores here, since reverse flow through a getter + // might have a different type here compared to inside the getter. + compatibleTypes(ap.getType(), contentType) + } + } + + private module Stage3 implements StageSig { + import MkStage::Stage + } + + private module Stage4Param implements MkStage::StageParam { + private module PrevStage = Stage3; + + class Ap = AccessPathFront; + + class ApNil = AccessPathFrontNil; + + PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } + + ApNil getApNil(NodeEx node) { + PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) + } + + bindingset[tc, tail] + Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + class ApHeadContent = Content; + + pragma[noinline] + ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + ApHeadContent projectToHeadContent(Content c) { result = c } + + class ApOption = AccessPathFrontOption; + + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + + import BooleanCallContext + + pragma[nomagic] + predicate localStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + ApNil ap, LocalCc lcc + ) { + localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), _) and + PrevStage::revFlow(node1, pragma[only_bind_into](state1), _) and + PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _) and + exists(lcc) + } + + pragma[nomagic] + predicate flowOutOfCall( + DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow + ) { + exists(FlowState state | + flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow) and + PrevStage::revFlow(node2, pragma[only_bind_into](state), _) and + PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _) + ) + } + + pragma[nomagic] + predicate flowIntoCall( + DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow + ) { + exists(FlowState state | + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow) and + PrevStage::revFlow(node2, pragma[only_bind_into](state), _) and + PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _) + ) + } + + pragma[nomagic] + private predicate clearSet(NodeEx node, ContentSet c) { + PrevStage::revFlow(node) and + clearsContentCached(node.asNode(), c) + } + + pragma[nomagic] + private predicate clearContent(NodeEx node, Content c) { + exists(ContentSet cs | + PrevStage::readStepCand(_, pragma[only_bind_into](c), _) and + c = cs.getAReadContent() and + clearSet(node, cs) + ) + } + + pragma[nomagic] + private predicate clear(NodeEx node, Ap ap) { clearContent(node, ap.getHead().getContent()) } + + pragma[nomagic] + private predicate expectsContentCand(NodeEx node, Ap ap) { + exists(Content c | + PrevStage::revFlow(node) and + PrevStage::readStepCand(_, c, _) and + expectsContentEx(node, c) and + c = ap.getHead().getContent() + ) + } + + pragma[nomagic] + private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } + + bindingset[node, state, ap] + predicate filter(NodeEx node, FlowState state, Ap ap) { + exists(state) and + not clear(node, ap) and + (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and + ( + notExpectsContent(node) + or + expectsContentCand(node, ap) + ) + } + + bindingset[ap, contentType] + predicate typecheckStore(Ap ap, DataFlowType contentType) { + // We need to typecheck stores here, since reverse flow through a getter + // might have a different type here compared to inside the getter. + compatibleTypes(ap.getType(), contentType) + } + } + + private module Stage4 implements StageSig { + import MkStage::Stage + } + + /** + * Holds if `argApf` is recorded as the summary context for flow reaching `node` + * and remains relevant for the following pruning stage. + */ + private predicate flowCandSummaryCtx(NodeEx node, FlowState state, AccessPathFront argApf) { + exists(AccessPathFront apf | + Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf) and + Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf) ) } /** - * Holds if `node1` can step to `node2` in one or more local steps and this - * path can occur as a maximal subsequence of local steps in a dataflow path. + * Holds if a length 2 access path approximation with the head `tc` is expected + * to be expensive. */ - pragma[nomagic] - predicate localFlowBigStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - DataFlowType t, Configuration config, LocalCallContext callContext - ) { - localFlowStepPlus(node1, state1, node2, preservesValue, t, config, callContext) and - localFlowExit(node2, state1, config) and - state1 = state2 - or - additionalLocalFlowStepNodeCand2(node1, state1, node2, state2, config) and - state1 != state2 and - preservesValue = false and - t = node2.getDataFlowType() and - callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) - } -} - -private import LocalFlowBigStep - -private module Stage3Param implements MkStage::StageParam { - private module PrevStage = Stage2; - - class Ap = ApproxAccessPathFront; - - class ApNil = ApproxAccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } - - class ApHeadContent = ContentApprox; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - predicate projectToHeadContent = getContentApprox/1; - - class ApOption = ApproxAccessPathFrontOption; - - ApOption apNone() { result = TApproxAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } - - import BooleanCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApproxAccessPathFrontNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand2/6; - - predicate flowIntoCall = flowIntoCallNodeCand2/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getAHead().getContent() + private predicate expensiveLen2unfolding(TypedContent tc) { + exists(int tails, int nodes, int apLimit, int tupleLimit | + tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf)) and + nodes = + strictcount(NodeEx n, FlowState state | + Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc)) + or + flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc)) + ) and + accessPathApproxCostLimits(apLimit, tupleLimit) and + apLimit < tails and + tupleLimit < (tails - 1) * nodes and + not tc.forceHighPrecision() ) } - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } + private newtype TAccessPathApprox = + TNil(DataFlowType t) or + TConsNil(TypedContent tc, DataFlowType t) { + Stage4::consCand(tc, TFrontNil(t)) and + not expensiveLen2unfolding(tc) + } or + TConsCons(TypedContent tc1, TypedContent tc2, int len) { + Stage4::consCand(tc1, TFrontHead(tc2)) and + len in [2 .. accessPathLimit()] and + not expensiveLen2unfolding(tc1) + } or + TCons1(TypedContent tc, int len) { + len in [1 .. accessPathLimit()] and + expensiveLen2unfolding(tc) + } - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) + /** + * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only + * the first two elements of the list and its length are tracked. If data flows + * from a source to a given node with a given `AccessPathApprox`, this indicates + * the sequence of dereference operations needed to get from the value in the node + * to the tracked object. The final type indicates the type of the tracked object. + */ + abstract private class AccessPathApprox extends TAccessPathApprox { + abstract string toString(); + + abstract TypedContent getHead(); + + abstract int len(); + + abstract DataFlowType getType(); + + abstract AccessPathFront getFront(); + + /** Gets the access path obtained by popping `head` from this path, if any. */ + abstract AccessPathApprox pop(TypedContent head); } - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} + private class AccessPathApproxNil extends AccessPathApprox, TNil { + private DataFlowType t; -private module Stage3 implements StageSig { - import MkStage::Stage -} + AccessPathApproxNil() { this = TNil(t) } -private module Stage4Param implements MkStage::StageParam { - private module PrevStage = Stage3; + override string toString() { result = concat(": " + ppReprType(t)) } - class Ap = AccessPathFront; + override TypedContent getHead() { none() } - class ApNil = AccessPathFrontNil; + override int len() { result = 0 } - PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } + override DataFlowType getType() { result = t } - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) + override AccessPathFront getFront() { result = TFrontNil(t) } + + override AccessPathApprox pop(TypedContent head) { none() } } - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + abstract private class AccessPathApproxCons extends AccessPathApprox { } - class ApHeadContent = Content; + private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { + private TypedContent tc; + private DataFlowType t; - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } + AccessPathApproxConsNil() { this = TConsNil(tc, t) } - ApHeadContent projectToHeadContent(Content c) { result = c } + override string toString() { + // The `concat` becomes "" if `ppReprType` has no result. + result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) + } - class ApOption = AccessPathFrontOption; + override TypedContent getHead() { result = tc } - ApOption apNone() { result = TAccessPathFrontNone() } + override int len() { result = 1 } - ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + override DataFlowType getType() { result = tc.getContainerType() } - import BooleanCallContext + override AccessPathFront getFront() { result = TFrontHead(tc) } - pragma[nomagic] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) and - exists(lcc) + override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } } - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } + private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { + private TypedContent tc1; + private TypedContent tc2; + private int len; - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } + AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - pragma[nomagic] - private predicate clearSet(NodeEx node, ContentSet c, Configuration config) { - PrevStage::revFlow(node, config) and - clearsContentCached(node.asNode(), c) - } + override string toString() { + if len = 2 + then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" + else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" + } - pragma[nomagic] - private predicate clearContent(NodeEx node, Content c, Configuration config) { - exists(ContentSet cs | - PrevStage::readStepCand(_, pragma[only_bind_into](c), _, pragma[only_bind_into](config)) and - c = cs.getAReadContent() and - clearSet(node, cs, pragma[only_bind_into](config)) - ) - } + override TypedContent getHead() { result = tc1 } - pragma[nomagic] - private predicate clear(NodeEx node, Ap ap, Configuration config) { - clearContent(node, ap.getHead().getContent(), config) - } + override int len() { result = len } - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getHead().getContent() - ) - } + override DataFlowType getType() { result = tc1.getContainerType() } - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } + override AccessPathFront getFront() { result = TFrontHead(tc1) } - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - not clear(node, ap, config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage4 implements StageSig { - import MkStage::Stage -} - -/** - * Holds if `argApf` is recorded as the summary context for flow reaching `node` - * and remains relevant for the following pruning stage. - */ -private predicate flowCandSummaryCtx( - NodeEx node, FlowState state, AccessPathFront argApf, Configuration config -) { - exists(AccessPathFront apf | - Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf, config) and - Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf, - config) - ) -} - -/** - * Holds if a length 2 access path approximation with the head `tc` is expected - * to be expensive. - */ -private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { - exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf, config)) and - nodes = - strictcount(NodeEx n, FlowState state | - Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - or - flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - ) and - accessPathApproxCostLimits(apLimit, tupleLimit) and - apLimit < tails and - tupleLimit < (tails - 1) * nodes and - not tc.forceHighPrecision() - ) -} - -private newtype TAccessPathApprox = - TNil(DataFlowType t) or - TConsNil(TypedContent tc, DataFlowType t) { - Stage4::consCand(tc, TFrontNil(t), _) and - not expensiveLen2unfolding(tc, _) - } or - TConsCons(TypedContent tc1, TypedContent tc2, int len) { - Stage4::consCand(tc1, TFrontHead(tc2), _) and - len in [2 .. accessPathLimit()] and - not expensiveLen2unfolding(tc1, _) - } or - TCons1(TypedContent tc, int len) { - len in [1 .. accessPathLimit()] and - expensiveLen2unfolding(tc, _) - } - -/** - * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only - * the first two elements of the list and its length are tracked. If data flows - * from a source to a given node with a given `AccessPathApprox`, this indicates - * the sequence of dereference operations needed to get from the value in the node - * to the tracked object. The final type indicates the type of the tracked object. - */ -abstract private class AccessPathApprox extends TAccessPathApprox { - abstract string toString(); - - abstract TypedContent getHead(); - - abstract int len(); - - abstract DataFlowType getType(); - - abstract AccessPathFront getFront(); - - /** Gets the access path obtained by popping `head` from this path, if any. */ - abstract AccessPathApprox pop(TypedContent head); -} - -private class AccessPathApproxNil extends AccessPathApprox, TNil { - private DataFlowType t; - - AccessPathApproxNil() { this = TNil(t) } - - override string toString() { result = concat(": " + ppReprType(t)) } - - override TypedContent getHead() { none() } - - override int len() { result = 0 } - - override DataFlowType getType() { result = t } - - override AccessPathFront getFront() { result = TFrontNil(t) } - - override AccessPathApprox pop(TypedContent head) { none() } -} - -abstract private class AccessPathApproxCons extends AccessPathApprox { } - -private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { - private TypedContent tc; - private DataFlowType t; - - AccessPathApproxConsNil() { this = TConsNil(tc, t) } - - override string toString() { - // The `concat` becomes "" if `ppReprType` has no result. - result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) - } - - override TypedContent getHead() { result = tc } - - override int len() { result = 1 } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } -} - -private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { - private TypedContent tc1; - private TypedContent tc2; - private int len; - - AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - - override string toString() { - if len = 2 - then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" - else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc1 } - - override int len() { result = len } - - override DataFlowType getType() { result = tc1.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc1) } - - override AccessPathApprox pop(TypedContent head) { - head = tc1 and - ( - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - } -} - -private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { - private TypedContent tc; - private int len; - - AccessPathApproxCons1() { this = TCons1(tc, len) } - - override string toString() { - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc } - - override int len() { result = len } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { - head = tc and - ( - exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2), _) | + override AccessPathApprox pop(TypedContent head) { + head = tc1 and + ( result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -2763,1561 +2471,585 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or result = TCons1(tc2, len - 1) ) - or - exists(DataFlowType t | - len = 1 and - Stage4::consCand(tc, TFrontNil(t), _) and - result = TNil(t) + } + } + + private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { + private TypedContent tc; + private int len; + + AccessPathApproxCons1() { this = TCons1(tc, len) } + + override string toString() { + if len = 1 + then result = "[" + tc.toString() + "]" + else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" + } + + override TypedContent getHead() { result = tc } + + override int len() { result = len } + + override DataFlowType getType() { result = tc.getContainerType() } + + override AccessPathFront getFront() { result = TFrontHead(tc) } + + override AccessPathApprox pop(TypedContent head) { + head = tc and + ( + exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2)) | + result = TConsCons(tc2, _, len - 1) + or + len = 2 and + result = TConsNil(tc2, _) + or + result = TCons1(tc2, len - 1) + ) + or + exists(DataFlowType t | + len = 1 and + Stage4::consCand(tc, TFrontNil(t)) and + result = TNil(t) + ) ) - ) + } } -} -/** Gets the access path obtained by popping `tc` from `ap`, if any. */ -private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } + /** Gets the access path obtained by popping `tc` from `ap`, if any. */ + private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } -/** Gets the access path obtained by pushing `tc` onto `ap`. */ -private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } + /** Gets the access path obtained by pushing `tc` onto `ap`. */ + private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } -private newtype TAccessPathApproxOption = - TAccessPathApproxNone() or - TAccessPathApproxSome(AccessPathApprox apa) + private newtype TAccessPathApproxOption = + TAccessPathApproxNone() or + TAccessPathApproxSome(AccessPathApprox apa) -private class AccessPathApproxOption extends TAccessPathApproxOption { - string toString() { - this = TAccessPathApproxNone() and result = "" - or - this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) + private class AccessPathApproxOption extends TAccessPathApproxOption { + string toString() { + this = TAccessPathApproxNone() and result = "" + or + this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) + } } -} -private module Stage5Param implements MkStage::StageParam { - private module PrevStage = Stage4; + private module Stage5Param implements MkStage::StageParam { + private module PrevStage = Stage4; - class Ap = AccessPathApprox; + class Ap = AccessPathApprox; - class ApNil = AccessPathApproxNil; + class ApNil = AccessPathApproxNil; + + pragma[nomagic] + PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } + + ApNil getApNil(NodeEx node) { + PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) + } + + bindingset[tc, tail] + Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + class ApHeadContent = Content; + + pragma[noinline] + ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + ApHeadContent projectToHeadContent(Content c) { result = c } + + class ApOption = AccessPathApproxOption; + + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + + import Level1CallContext + import LocalCallContext + + predicate localStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + ApNil ap, LocalCc lcc + ) { + localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), lcc) and + PrevStage::revFlow(node1, pragma[only_bind_into](state1), _) and + PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _) + } + + pragma[nomagic] + predicate flowOutOfCall( + DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow + ) { + exists(FlowState state | + flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow) and + PrevStage::revFlow(node2, pragma[only_bind_into](state), _) and + PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _) + ) + } + + pragma[nomagic] + predicate flowIntoCall( + DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow + ) { + exists(FlowState state | + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow) and + PrevStage::revFlow(node2, pragma[only_bind_into](state), _) and + PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _) + ) + } + + bindingset[node, state, ap] + predicate filter(NodeEx node, FlowState state, Ap ap) { any() } + + // Type checking is not necessary here as it has already been done in stage 3. + bindingset[ap, contentType] + predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + } + + private module Stage5 = MkStage::Stage; pragma[nomagic] - PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathApproxOption; - - ApOption apNone() { result = TAccessPathApproxNone() } - - ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } - - import Level1CallContext - import LocalCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc + private predicate nodeMayUseSummary0( + NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, lcc) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) + exists(AccessPathApprox apa0 | + Stage5::parameterMayFlowThrough(p, _) and + Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0) and + Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), + TAccessPathApproxSome(apa), apa0) ) } pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) + private predicate nodeMayUseSummary(NodeEx n, FlowState state, AccessPathApprox apa) { + exists(ParamNodeEx p | + Stage5::parameterMayFlowThrough(p, apa) and + nodeMayUseSummary0(n, p, state, apa) ) } - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { any() } + private newtype TSummaryCtx = + TSummaryCtxNone() or + TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { + Stage5::parameterMayFlowThrough(p, ap.getApprox()) and + Stage5::revFlow(p, state, _) + } - // Type checking is not necessary here as it has already been done in stage 3. - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} + /** + * A context for generating flow summaries. This represents flow entry through + * a specific parameter with an access path of a specific shape. + * + * Summaries are only created for parameters that may flow through. + */ + abstract private class SummaryCtx extends TSummaryCtx { + abstract string toString(); + } -private module Stage5 = MkStage::Stage; + /** A summary context from which no flow summary can be generated. */ + private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { + override string toString() { result = "" } + } -bindingset[conf, result] -private Configuration unbindConf(Configuration conf) { - exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c)) -} + /** A summary context from which a flow summary can be generated. */ + private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { + private ParamNodeEx p; + private FlowState s; + private AccessPath ap; -pragma[nomagic] -private predicate nodeMayUseSummary0( - NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(AccessPathApprox apa0 | - Stage5::parameterMayFlowThrough(p, _, _) and - Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0, config) and - Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), - TAccessPathApproxSome(apa), apa0, config) - ) -} + SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } -pragma[nomagic] -private predicate nodeMayUseSummary( - NodeEx n, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::parameterMayFlowThrough(p, apa, config) and - nodeMayUseSummary0(n, p, state, apa, config) - ) -} + ParameterPosition getParameterPos() { p.isParameterOf(_, result) } -private newtype TSummaryCtx = - TSummaryCtxNone() or - TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { - exists(Configuration config | - Stage5::parameterMayFlowThrough(p, ap.getApprox(), config) and - Stage5::revFlow(p, state, _, config) + ParamNodeEx getParamNode() { result = p } + + override string toString() { result = p + ": " + ap } + + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * Gets the number of length 2 access path approximations that correspond to `apa`. + */ + private int count1to2unfold(AccessPathApproxCons1 apa) { + exists(TypedContent tc, int len | + tc = apa.getHead() and + len = apa.len() and + result = + strictcount(AccessPathFront apf | + Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1)) + ) ) } -/** - * A context for generating flow summaries. This represents flow entry through - * a specific parameter with an access path of a specific shape. - * - * Summaries are only created for parameters that may flow through. - */ -abstract private class SummaryCtx extends TSummaryCtx { - abstract string toString(); -} - -/** A summary context from which no flow summary can be generated. */ -private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { - override string toString() { result = "" } -} - -/** A summary context from which a flow summary can be generated. */ -private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParamNodeEx p; - private FlowState s; - private AccessPath ap; - - SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } - - ParameterPosition getParameterPos() { p.isParameterOf(_, result) } - - ParamNodeEx getParamNode() { result = p } - - override string toString() { result = p + ": " + ap } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** - * Gets the number of length 2 access path approximations that correspond to `apa`. - */ -private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { - exists(TypedContent tc, int len | - tc = apa.getHead() and - len = apa.len() and + private int countNodesUsingAccessPath(AccessPathApprox apa) { result = - strictcount(AccessPathFront apf | - Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), - config) + strictcount(NodeEx n, FlowState state | + Stage5::revFlow(n, state, apa) or nodeMayUseSummary(n, state, apa) ) - ) -} + } -private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = - strictcount(NodeEx n, FlowState state | - Stage5::revFlow(n, state, apa, config) or nodeMayUseSummary(n, state, apa, config) - ) -} - -/** - * Holds if a length 2 access path approximation matching `apa` is expected - * to be expensive. - */ -private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = count1to2unfold(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - apLimit < aps and - tupleLimit < (aps - 1) * nodes - ) -} - -private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { - exists(TypedContent head | - apa.pop(head) = result and - Stage5::consCand(head, result, config) - ) -} - -/** - * Holds with `unfold = false` if a precise head-tail representation of `apa` is - * expected to be expensive. Holds with `unfold = true` otherwise. - */ -private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { - if apa.getHead().forceHighPrecision() - then unfold = true - else + /** + * Holds if a length 2 access path approximation matching `apa` is expected + * to be expensive. + */ + private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa) { exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = countPotentialAps(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and + aps = count1to2unfold(apa) and + nodes = countNodesUsingAccessPath(apa) and accessPathCostLimits(apLimit, tupleLimit) and - if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true - ) -} - -/** - * Gets the number of `AccessPath`s that correspond to `apa`. - */ -pragma[assume_small_delta] -private int countAps(AccessPathApprox apa, Configuration config) { - evalUnfold(apa, false, config) and - result = 1 and - (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) - or - evalUnfold(apa, false, config) and - result = count1to2unfold(apa, config) and - not expensiveLen1to2unfolding(apa, config) - or - evalUnfold(apa, true, config) and - result = countPotentialAps(apa, config) -} - -/** - * Gets the number of `AccessPath`s that would correspond to `apa` assuming - * that it is expanded to a precise head-tail representation. - */ -language[monotonicAggregates] -pragma[assume_small_delta] -private int countPotentialAps(AccessPathApprox apa, Configuration config) { - apa instanceof AccessPathApproxNil and result = 1 - or - result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) -} - -private newtype TAccessPath = - TAccessPathNil(DataFlowType t) or - TAccessPathCons(TypedContent head, AccessPath tail) { - exists(AccessPathApproxCons apa | - not evalUnfold(apa, false, _) and - head = apa.getHead() and - tail.getApprox() = getATail(apa, _) - ) - } or - TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - not expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head1 = apa.getHead() and - head2 = getATail(apa, _).getHead() - ) - } or - TAccessPathCons1(TypedContent head, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head = apa.getHead() + apLimit < aps and + tupleLimit < (aps - 1) * nodes ) } -private newtype TPathNode = - pragma[assume_small_delta] - TPathNodeMid( - NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config - ) { - // A PathNode is introduced by a source ... - Stage5::revFlow(node, state, config) and - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - or - // ... or a step from an existing PathNode to another node. - exists(PathNodeMid mid | - pathStep(mid, node, state, cc, sc, ap) and - pragma[only_bind_into](config) = mid.getConfiguration() and - Stage5::revFlow(node, state, ap.getApprox(), pragma[only_bind_into](config)) - ) - } or - TPathNodeSink(NodeEx node, FlowState state, Configuration config) { - exists(PathNodeMid sink | - sink.isAtSink() and - node = sink.getNodeEx() and - state = sink.getState() and - config = sink.getConfiguration() - ) - } or - TPathNodeSourceGroup(string sourceGroup, Configuration config) { - exists(PathNodeImpl source | - sourceGroup = source.getSourceGroup() and - config = source.getConfiguration() - ) - } or - TPathNodeSinkGroup(string sinkGroup, Configuration config) { - exists(PathNodeSink sink | - sinkGroup = sink.getSinkGroup() and - config = sink.getConfiguration() + private AccessPathApprox getATail(AccessPathApprox apa) { + exists(TypedContent head | + apa.pop(head) = result and + Stage5::consCand(head, result) ) } -/** - * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a - * source to a given node with a given `AccessPath`, this indicates the sequence - * of dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ -private class AccessPath extends TAccessPath { - /** Gets the head of this access path, if any. */ - abstract TypedContent getHead(); - - /** Gets the tail of this access path, if any. */ - abstract AccessPath getTail(); - - /** Gets the front of this access path. */ - abstract AccessPathFront getFront(); - - /** Gets the approximation of this access path. */ - abstract AccessPathApprox getApprox(); - - /** Gets the length of this access path. */ - abstract int length(); - - /** Gets a textual representation of this access path. */ - abstract string toString(); - - /** Gets the access path obtained by popping `tc` from this access path, if any. */ - final AccessPath pop(TypedContent tc) { - result = this.getTail() and - tc = this.getHead() - } - - /** Gets the access path obtained by pushing `tc` onto this access path. */ - final AccessPath push(TypedContent tc) { this = result.pop(tc) } -} - -private class AccessPathNil extends AccessPath, TAccessPathNil { - private DataFlowType t; - - AccessPathNil() { this = TAccessPathNil(t) } - - DataFlowType getType() { result = t } - - override TypedContent getHead() { none() } - - override AccessPath getTail() { none() } - - override AccessPathFrontNil getFront() { result = TFrontNil(t) } - - override AccessPathApproxNil getApprox() { result = TNil(t) } - - override int length() { result = 0 } - - override string toString() { result = concat(": " + ppReprType(t)) } -} - -private class AccessPathCons extends AccessPath, TAccessPathCons { - private TypedContent head; - private AccessPath tail; - - AccessPathCons() { this = TAccessPathCons(head, tail) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { result = tail } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - pragma[assume_small_delta] - override AccessPathApproxCons getApprox() { - result = TConsNil(head, tail.(AccessPathNil).getType()) - or - result = TConsCons(head, tail.getHead(), this.length()) - or - result = TCons1(head, this.length()) - } - - pragma[assume_small_delta] - override int length() { result = 1 + tail.length() } - - private string toStringImpl(boolean needsSuffix) { - exists(DataFlowType t | - tail = TAccessPathNil(t) and - needsSuffix = false and - result = head.toString() + "]" + concat(" : " + ppReprType(t)) - ) - or - result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) - or - exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | - result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true - or - result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false - ) - or - exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | - result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true - or - result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false - ) - } - - override string toString() { - result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" - or - result = "[" + this.toStringImpl(false) - } -} - -private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { - private TypedContent head1; - private TypedContent head2; - private int len; - - AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } - - override TypedContent getHead() { result = head1 } - - override AccessPath getTail() { - Stage5::consCand(head1, result.getApprox(), _) and - result.getHead() = head2 and - result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head1) } - - override AccessPathApproxCons getApprox() { - result = TConsCons(head1, head2, len) or - result = TCons1(head1, len) - } - - override int length() { result = len } - - override string toString() { - if len = 2 - then result = "[" + head1.toString() + ", " + head2.toString() + "]" + /** + * Holds with `unfold = false` if a precise head-tail representation of `apa` is + * expected to be expensive. Holds with `unfold = true` otherwise. + */ + private predicate evalUnfold(AccessPathApprox apa, boolean unfold) { + if apa.getHead().forceHighPrecision() + then unfold = true else - result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" - } -} - -private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { - private TypedContent head; - private int len; - - AccessPathCons1() { this = TAccessPathCons1(head, len) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { - Stage5::consCand(head, result.getApprox(), _) and result.length() = len - 1 + exists(int aps, int nodes, int apLimit, int tupleLimit | + aps = countPotentialAps(apa) and + nodes = countNodesUsingAccessPath(apa) and + accessPathCostLimits(apLimit, tupleLimit) and + if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true + ) } - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - - override int length() { result = len } - - override string toString() { - if len = 1 - then result = "[" + head.toString() + "]" - else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" - } -} - -abstract private class PathNodeImpl extends TPathNode { - /** Gets the `FlowState` of this node. */ - abstract FlowState getState(); - - /** Gets the associated configuration. */ - abstract Configuration getConfiguration(); - - /** Holds if this node is a source. */ - abstract predicate isSource(); - - abstract PathNodeImpl getASuccessorImpl(); - - private PathNodeImpl getASuccessorIfHidden() { - this.isHidden() and - result = this.getASuccessorImpl() - } - - pragma[nomagic] - private PathNodeImpl getANonHiddenSuccessor0() { - result = this.getASuccessorIfHidden*() and - not result.isHidden() - } - - final PathNodeImpl getANonHiddenSuccessor() { - result = this.getASuccessorImpl().getANonHiddenSuccessor0() and - not this.isHidden() - } - - abstract NodeEx getNodeEx(); - - predicate isHidden() { - not this.getConfiguration().includeHiddenNodes() and - ( - hiddenNode(this.getNodeEx().asNode()) and - not this.isSource() and - not this instanceof PathNodeSink - or - this.getNodeEx() instanceof TNodeImplicitRead - ) - } - - string getSourceGroup() { - this.isSource() and - this.getConfiguration().sourceGrouping(this.getNodeEx().asNode(), result) - } - - predicate isFlowSource() { - this.isSource() and not exists(this.getSourceGroup()) + /** + * Gets the number of `AccessPath`s that correspond to `apa`. + */ + pragma[assume_small_delta] + private int countAps(AccessPathApprox apa) { + evalUnfold(apa, false) and + result = 1 and + (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa)) or - this instanceof PathNodeSourceGroup - } - - predicate isFlowSink() { - this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or - this instanceof PathNodeSinkGroup - } - - private string ppAp() { - this instanceof PathNodeSink and result = "" + evalUnfold(apa, false) and + result = count1to2unfold(apa) and + not expensiveLen1to2unfolding(apa) or - exists(string s | s = this.(PathNodeMid).getAp().toString() | - if s = "" then result = "" else result = " " + s - ) + evalUnfold(apa, true) and + result = countPotentialAps(apa) } - private string ppCtx() { - this instanceof PathNodeSink and result = "" + /** + * Gets the number of `AccessPath`s that would correspond to `apa` assuming + * that it is expanded to a precise head-tail representation. + */ + language[monotonicAggregates] + pragma[assume_small_delta] + private int countPotentialAps(AccessPathApprox apa) { + apa instanceof AccessPathApproxNil and result = 1 or - result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + result = strictsum(AccessPathApprox tail | tail = getATail(apa) | countAps(tail)) } - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** Holds if `n` can reach a sink. */ -private predicate directReach(PathNodeImpl n) { - n instanceof PathNodeSink or - n instanceof PathNodeSinkGroup or - directReach(n.getANonHiddenSuccessor()) -} - -/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ -private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } - -/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { - n1.getANonHiddenSuccessor() = n2 and directReach(n2) -} - -private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) - -/** - * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. - * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. - */ -class PathNode instanceof PathNodeImpl { - PathNode() { reach(this) } - - /** Gets a textual representation of this element. */ - final string toString() { result = super.toString() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - final string toStringWithContext() { result = super.toStringWithContext() } - - /** - * 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/). - */ - final predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - /** Gets the underlying `Node`. */ - final Node getNode() { super.getNodeEx().projectToNode() = result } - - /** Gets the `FlowState` of this node. */ - final FlowState getState() { result = super.getState() } - - /** Gets the associated configuration. */ - final Configuration getConfiguration() { result = super.getConfiguration() } - - /** Gets a successor of this node, if any. */ - final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } - - /** Holds if this node is a source. */ - final predicate isSource() { super.isSource() } - - /** Holds if this node is a grouping of source nodes. */ - final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group, _) } - - /** Holds if this node is a grouping of sink nodes. */ - final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group, _) } -} - -/** - * Provides the query predicates needed to include a graph in a path-problem query. - */ -module PathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } - - /** Holds if `n` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode n, string key, string val) { - key = "semmle.label" and val = n.toString() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { - Subpaths::subpaths(arg, par, ret, out) - } -} - -/** - * An intermediate flow graph node. This is a triple consisting of a `Node`, - * a `CallContext`, and a `Configuration`. - */ -private class PathNodeMid extends PathNodeImpl, TPathNodeMid { - NodeEx node; - FlowState state; - CallContext cc; - SummaryCtx sc; - AccessPath ap; - Configuration config; - - PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - SummaryCtx getSummaryCtx() { result = sc } - - AccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - private PathNodeMid getSuccMid() { - pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx(), result.getAp()) and - result.getConfiguration() = unbindConf(this.getConfiguration()) - } - - override PathNodeImpl getASuccessorImpl() { - // an intermediate step to another intermediate node - result = this.getSuccMid() - or - // a final step to a sink - result = this.getSuccMid().projectToSink() - } - - override predicate isSource() { - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - } - - predicate isAtSink() { - sinkNode(node, state, config) and - ap instanceof AccessPathNil and - if hasSinkCallCtx(config) - then - // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` - // is exactly what we need to check. This also implies - // `sc instanceof SummaryCtxNone`. - // For `FeatureEqualSourceSinkCallContext` the initial call context was - // set to `CallContextSomeCall` and jumps are disallowed, so - // `cc instanceof CallContextNoCall` never holds. On the other hand, - // in this case there's never any need to enter a call except to identify - // a summary, so the condition in `pathIntoCallable` enforces this, which - // means that `sc instanceof SummaryCtxNone` holds if and only if we are - // in the call context of the source. - sc instanceof SummaryCtxNone or - cc instanceof CallContextNoCall - else any() - } - - PathNodeSink projectToSink() { - this.isAtSink() and - result.getNodeEx() = node and - result.getState() = state and - result.getConfiguration() = unbindConf(config) - } -} - -/** - * A flow graph node corresponding to a sink. This is disjoint from the - * intermediate nodes in order to uniquely correspond to a given sink by - * excluding the `CallContext`. - */ -private class PathNodeSink extends PathNodeImpl, TPathNodeSink { - NodeEx node; - FlowState state; - Configuration config; - - PathNodeSink() { this = TPathNodeSink(node, state, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result = TPathNodeSinkGroup(this.getSinkGroup(), config) - } - - override predicate isSource() { sourceNode(node, state, config) } - - string getSinkGroup() { config.sinkGrouping(node.asNode(), result) } -} - -private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { - string sourceGroup; - Configuration config; - - PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result.getSourceGroup() = sourceGroup and - result.getConfiguration() = config - } - - override predicate isSource() { none() } - - override string toString() { result = sourceGroup } - - override 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 - } -} - -private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { - string sinkGroup; - Configuration config; - - PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { none() } - - override predicate isSource() { none() } - - override string toString() { result = sinkGroup } - - override 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 - } -} - -private predicate pathNode( - PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, - Configuration conf, LocalCallContext localCC -) { - midnode = mid.getNodeEx() and - state = mid.getState() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - midnode.getEnclosingCallable()) and - ap = mid.getAp() -} - -/** - * Holds if data may flow from `mid` to `node`. The last step in or out of - * a callable is recorded by `cc`. - */ -pragma[assume_small_delta] -pragma[nomagic] -private predicate pathStep( - PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap -) { - exists(NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC | - pathNode(mid, midnode, state0, cc, sc, ap, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, true, _, conf, localCC) - ) - or - exists( - AccessPath ap0, NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC - | - pathNode(mid, midnode, state0, cc, sc, ap0, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), conf, - localCC) and - ap0 instanceof AccessPathNil - ) - or - jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - ap = mid.getAp() - or - additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, mid.getConfiguration()) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - pathIntoCallable(mid, node, state, _, cc, sc, _, _) and ap = mid.getAp() - or - pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone - or - pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() -} - -pragma[nomagic] -private predicate pathReadStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - tc = ap0.getHead() and - Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -pragma[nomagic] -private predicate pathStoreStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -private predicate pathOutOfCallable0( - PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa, - Configuration config -) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - apa = mid.getAp().getApprox() and - config = mid.getConfiguration() -} - -pragma[nomagic] -private predicate pathOutOfCallable1( - PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPathApprox apa, Configuration config -) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - pathOutOfCallable0(mid, pos, state, innercc, apa, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) -} - -pragma[noinline] -private NodeEx getAnOutNodeFlow( - ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config -) { - result.asNode() = kind.getAnOutNode(call) and - Stage5::revFlow(result, _, apa, config) -} - -/** - * Holds if data may flow from `mid` to `out`. The last step of this path - * is a return from a callable and is recorded by `cc`, if needed. - */ -pragma[noinline] -private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { - exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | - pathOutOfCallable1(mid, call, kind, state, cc, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. - */ -pragma[noinline] -private predicate pathIntoArg( - PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(ArgNodeEx arg, ArgumentPosition apos | - pathNode(mid, arg, state, cc, _, ap, config, _) and - arg.asNode().(ArgNode).argumentOf(call, apos) and - apa = ap.getApprox() and - parameterMatch(ppos, apos) - ) -} - -pragma[nomagic] -private predicate parameterCand( - DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::revFlow(p, _, apa, config) and - p.isParameterOf(callable, pos) - ) -} - -pragma[nomagic] -private predicate pathIntoCallable0( - PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, AccessPath ap, Configuration config -) { - exists(AccessPathApprox apa | - pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - callable = resolveCall(call, outercc) and - parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa), - pragma[only_bind_into](config)) - ) -} - -/** - * Holds if data may flow from `mid` to `p` through `call`. The contexts - * before and after entering the callable are `outercc` and `innercc`, - * respectively. - */ -pragma[nomagic] -private predicate pathIntoCallable( - PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, - SummaryCtx sc, DataFlowCall call, Configuration config -) { - exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | - pathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - ( - sc = TSummaryCtxSome(p, state, ap) - or - not exists(TSummaryCtxSome(p, state, ap)) and - sc = TSummaryCtxNone() and - // When the call contexts of source and sink needs to match then there's - // never any reason to enter a callable except to find a summary. See also - // the comment in `PathNodeMid::isAtSink`. - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) -} - -/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ -pragma[nomagic] -private predicate paramFlowsThrough( - ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, - AccessPathApprox apa, Configuration config -) { - exists(RetNodeEx ret | - pathNode(_, ret, state, cc, sc, ap, config, _) and - kind = ret.getKind() and - apa = ap.getApprox() and - parameterFlowThroughAllowed(sc.getParamNode(), kind) - ) -} - -pragma[nomagic] -private predicate pathThroughCallable0( - DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(CallContext innercc, SummaryCtx sc | - pathIntoCallable(mid, _, _, cc, innercc, sc, call, config) and - paramFlowsThrough(kind, state, innercc, sc, ap, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` through a callable to the node `out`. - * The context `cc` is restored to its value prior to entering the callable. - */ -pragma[noinline] -private predicate pathThroughCallable( - PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap -) { - exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config | - pathThroughCallable0(call, mid, kind, state, cc, ap, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -private module Subpaths { - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths01( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - exists(Configuration config | - pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and - pathIntoCallable(arg, par, _, _, innercc, sc, _, config) and - paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, - pragma[only_bind_into](apout), _, unbindConf(config)) and - not arg.isHidden() - ) - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `sout`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths02( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and - out.asNode() = kind.getAnOutNode(_) - } - - pragma[nomagic] - private Configuration getPathNodeConf(PathNodeImpl n) { result = n.getConfiguration() } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple. - */ - pragma[nomagic] - private predicate subpaths03( - PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, AccessPath apout - ) { - exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | - subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and - pathNode(ret, retnode, sout, innercc, sc, apout, unbindConf(getPathNodeConf(arg)), _) and - kind = retnode.getKind() - ) - } - - private PathNodeImpl localStepToHidden(PathNodeImpl n) { - n.getASuccessorImpl() = result and - result.isHidden() and - exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | - localFlowBigStep(n1, _, n2, _, _, _, _, _) or - store(n1, _, n2, _, _) or - readSet(n1, _, n2, _) - ) - } - - pragma[nomagic] - private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { - succ = pred.getANonHiddenSuccessor() and - succNode = succ.getNodeEx() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { - exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | - pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and - subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and - hasSuccessor(pragma[only_bind_into](arg), par, p) and - not ret.isHidden() and - pathNode(out0, o, sout, _, _, apout, _, _) - | - out = out0 or out = out0.projectToSink() - ) - } - - /** - * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. - */ - predicate retReach(PathNodeImpl n) { - exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) - or - exists(PathNodeImpl mid | - retReach(mid) and - n.getANonHiddenSuccessor() = mid and - not subpaths(_, mid, _, _) - ) - } -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -private predicate hasFlowPath( - PathNodeImpl flowsource, PathNodeImpl flowsink, Configuration configuration -) { - flowsource.isFlowSource() and - flowsource.getConfiguration() = configuration and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.isFlowSink() -} - -private predicate flowsTo( - PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink, - Configuration configuration -) { - flowsource.isSource() and - flowsource.getConfiguration() = configuration and - flowsource.getNodeEx().asNode() = source and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.getNodeEx().asNode() = sink -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -predicate flowsTo(Node source, Node sink, Configuration configuration) { - flowsTo(_, _, source, sink, configuration) -} - -private predicate finalStats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples -) { - fwd = true and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and - tuples = count(PathNodeImpl pn) - or - fwd = false and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and - tuples = count(PathNode pn) -} - -/** - * INTERNAL: Only for debugging. - * - * Calculates per-stage metrics for data flow. - */ -predicate stageStats( - int n, string stage, int nodes, int fields, int conscand, int states, int tuples, - Configuration config -) { - stage = "1 Fwd" and - n = 10 and - Stage1::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "1 Rev" and - n = 15 and - Stage1::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Fwd" and - n = 20 and - Stage2::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Rev" and - n = 25 and - Stage2::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Fwd" and - n = 30 and - Stage3::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Rev" and - n = 35 and - Stage3::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Fwd" and - n = 40 and - Stage4::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Rev" and - n = 45 and - Stage4::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Fwd" and - n = 50 and - Stage5::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Rev" and - n = 55 and - Stage5::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) - or - stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) -} - -private module FlowExploration { - private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - jumpStep(node1, node2, config) - or - additionalJumpStep(node1, node2, config) - or - additionalJumpStateStep(node1, _, node2, _, config) - or - // flow into callable - viableParamArgEx(_, node2, node1) - or - // flow out of a callable - viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) - | - c1 = node1.getEnclosingCallable() and - c2 = node2.getEnclosingCallable() and - c1 != c2 - ) - } - - private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSource(n) or config.isSource(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSrc(mid, config) and callableStep(mid, c, config) - ) - } - - private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSink(n) or config.isSink(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSink(mid, config) and callableStep(c, mid, config) - ) - } - - private newtype TCallableExt = - TCallable(DataFlowCallable c, Configuration config) { - interestingCallableSrc(c, config) or - interestingCallableSink(c, config) + private newtype TAccessPath = + TAccessPathNil(DataFlowType t) or + TAccessPathCons(TypedContent head, AccessPath tail) { + exists(AccessPathApproxCons apa | + not evalUnfold(apa, false) and + head = apa.getHead() and + tail.getApprox() = getATail(apa) + ) } or - TCallableSrc() or - TCallableSink() + TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { + exists(AccessPathApproxCons apa | + evalUnfold(apa, false) and + not expensiveLen1to2unfolding(apa) and + apa.len() = len and + head1 = apa.getHead() and + head2 = getATail(apa).getHead() + ) + } or + TAccessPathCons1(TypedContent head, int len) { + exists(AccessPathApproxCons apa | + evalUnfold(apa, false) and + expensiveLen1to2unfolding(apa) and + apa.len() = len and + head = apa.getHead() + ) + } - private predicate callableExtSrc(TCallableSrc src) { any() } - - private predicate callableExtSink(TCallableSink sink) { any() } - - private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { - exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | - callableStep(c1, c2, config) and - ce1 = TCallable(c1, pragma[only_bind_into](config)) and - ce2 = TCallable(c2, pragma[only_bind_into](config)) - ) - or - exists(Node n, Configuration config | - ce1 = TCallableSrc() and - (config.isSource(n) or config.isSource(n, _)) and - ce2 = TCallable(getNodeEnclosingCallable(n), config) - ) - or - exists(Node n, Configuration config | - ce2 = TCallableSink() and - (config.isSink(n) or config.isSink(n, _)) and - ce1 = TCallable(getNodeEnclosingCallable(n), config) - ) - } - - private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { - callableExtStepFwd(ce2, ce1) - } - - private int distSrcExt(TCallableExt c) = - shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) - - private int distSinkExt(TCallableExt c) = - shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) - - private int distSrc(DataFlowCallable c, Configuration config) { - result = distSrcExt(TCallable(c, config)) - 1 - } - - private int distSink(DataFlowCallable c, Configuration config) { - result = distSinkExt(TCallable(c, config)) - 1 - } - - private newtype TPartialAccessPath = - TPartialNil(DataFlowType t) or - TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } + private newtype TPathNode = + pragma[assume_small_delta] + TPathNodeMid(NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap) { + // A PathNode is introduced by a source ... + Stage5::revFlow(node, state) and + sourceNode(node, state) and + ( + if hasSourceCallCtx() + then cc instanceof CallContextSomeCall + else cc instanceof CallContextAny + ) and + sc instanceof SummaryCtxNone and + ap = TAccessPathNil(node.getDataFlowType()) + or + // ... or a step from an existing PathNode to another node. + pathStep(_, node, state, cc, sc, ap) and + Stage5::revFlow(node, state, ap.getApprox()) + } or + TPathNodeSink(NodeEx node, FlowState state) { + exists(PathNodeMid sink | + sink.isAtSink() and + node = sink.getNodeEx() and + state = sink.getState() + ) + } or + TPathNodeSourceGroup(string sourceGroup) { + exists(PathNodeImpl source | sourceGroup = source.getSourceGroup()) + } or + TPathNodeSinkGroup(string sinkGroup) { + exists(PathNodeSink sink | sinkGroup = sink.getSinkGroup()) + } /** - * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first - * element of the list and its length are tracked. If data flows from a source to - * a given node with a given `AccessPath`, this indicates the sequence of - * dereference operations needed to get from the value in the node to the + * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a + * source to a given node with a given `AccessPath`, this indicates the sequence + * of dereference operations needed to get from the value in the node to the * tracked object. The final type indicates the type of the tracked object. */ - private class PartialAccessPath extends TPartialAccessPath { + private class AccessPath extends TAccessPath { + /** Gets the head of this access path, if any. */ + abstract TypedContent getHead(); + + /** Gets the tail of this access path, if any. */ + abstract AccessPath getTail(); + + /** Gets the front of this access path. */ + abstract AccessPathFront getFront(); + + /** Gets the approximation of this access path. */ + abstract AccessPathApprox getApprox(); + + /** Gets the length of this access path. */ + abstract int length(); + + /** Gets a textual representation of this access path. */ abstract string toString(); - TypedContent getHead() { this = TPartialCons(result, _) } - - int len() { - this = TPartialNil(_) and result = 0 - or - this = TPartialCons(_, result) + /** Gets the access path obtained by popping `tc` from this access path, if any. */ + final AccessPath pop(TypedContent tc) { + result = this.getTail() and + tc = this.getHead() } - DataFlowType getType() { - this = TPartialNil(result) - or - exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) - } + /** Gets the access path obtained by pushing `tc` onto this access path. */ + final AccessPath push(TypedContent tc) { this = result.pop(tc) } } - private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { - override string toString() { - exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) - } + private class AccessPathNil extends AccessPath, TAccessPathNil { + private DataFlowType t; + + AccessPathNil() { this = TAccessPathNil(t) } + + DataFlowType getType() { result = t } + + override TypedContent getHead() { none() } + + override AccessPath getTail() { none() } + + override AccessPathFrontNil getFront() { result = TFrontNil(t) } + + override AccessPathApproxNil getApprox() { result = TNil(t) } + + override int length() { result = 0 } + + override string toString() { result = concat(": " + ppReprType(t)) } } - private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { - override string toString() { - exists(TypedContent tc, int len | this = TPartialCons(tc, len) | - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" + private class AccessPathCons extends AccessPath, TAccessPathCons { + private TypedContent head; + private AccessPath tail; + + AccessPathCons() { this = TAccessPathCons(head, tail) } + + override TypedContent getHead() { result = head } + + override AccessPath getTail() { result = tail } + + override AccessPathFrontHead getFront() { result = TFrontHead(head) } + + pragma[assume_small_delta] + override AccessPathApproxCons getApprox() { + result = TConsNil(head, tail.(AccessPathNil).getType()) + or + result = TConsCons(head, tail.getHead(), this.length()) + or + result = TCons1(head, this.length()) + } + + pragma[assume_small_delta] + override int length() { result = 1 + tail.length() } + + private string toStringImpl(boolean needsSuffix) { + exists(DataFlowType t | + tail = TAccessPathNil(t) and + needsSuffix = false and + result = head.toString() + "]" + concat(" : " + ppReprType(t)) + ) + or + result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) + or + exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | + result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true + or + result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false + ) + or + exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | + result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true + or + result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false ) } - } - private newtype TRevPartialAccessPath = - TRevPartialNil() or - TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `Content`s, but only the first - * element of the list and its length are tracked. - */ - private class RevPartialAccessPath extends TRevPartialAccessPath { - abstract string toString(); - - Content getHead() { this = TRevPartialCons(result, _) } - - int len() { - this = TRevPartialNil() and result = 0 - or - this = TRevPartialCons(_, result) - } - } - - private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { - override string toString() { result = "" } - } - - private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { override string toString() { - exists(Content c, int len | this = TRevPartialCons(c, len) | - if len = 1 - then result = "[" + c.toString() + "]" - else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" - ) + result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" + or + result = "[" + this.toStringImpl(false) } } - private predicate relevantState(FlowState state) { - sourceNode(_, state, _) or - sinkNode(_, state, _) or - additionalLocalStateStep(_, state, _, _, _) or - additionalLocalStateStep(_, _, _, state, _) or - additionalJumpStateStep(_, state, _, _, _) or - additionalJumpStateStep(_, _, _, state, _) + private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { + private TypedContent head1; + private TypedContent head2; + private int len; + + AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } + + override TypedContent getHead() { result = head1 } + + override AccessPath getTail() { + Stage5::consCand(head1, result.getApprox()) and + result.getHead() = head2 and + result.length() = len - 1 + } + + override AccessPathFrontHead getFront() { result = TFrontHead(head1) } + + override AccessPathApproxCons getApprox() { + result = TConsCons(head1, head2, len) or + result = TCons1(head1, len) + } + + override int length() { result = len } + + override string toString() { + if len = 2 + then result = "[" + head1.toString() + ", " + head2.toString() + "]" + else + result = + "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" + } } - private newtype TSummaryCtx1 = - TSummaryCtx1None() or - TSummaryCtx1Param(ParamNodeEx p) + private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { + private TypedContent head; + private int len; - private newtype TSummaryCtx2 = - TSummaryCtx2None() or - TSummaryCtx2Some(FlowState s) { relevantState(s) } + AccessPathCons1() { this = TAccessPathCons1(head, len) } - private newtype TSummaryCtx3 = - TSummaryCtx3None() or - TSummaryCtx3Some(PartialAccessPath ap) + override TypedContent getHead() { result = head } - private newtype TRevSummaryCtx1 = - TRevSummaryCtx1None() or - TRevSummaryCtx1Some(ReturnPosition pos) + override AccessPath getTail() { + Stage5::consCand(head, result.getApprox()) and result.length() = len - 1 + } - private newtype TRevSummaryCtx2 = - TRevSummaryCtx2None() or - TRevSummaryCtx2Some(FlowState s) { relevantState(s) } + override AccessPathFrontHead getFront() { result = TFrontHead(head) } - private newtype TRevSummaryCtx3 = - TRevSummaryCtx3None() or - TRevSummaryCtx3Some(RevPartialAccessPath ap) + override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - private newtype TPartialPathNode = - TPartialPathNodeFwd( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = TPartialNil(node.getDataFlowType()) and - exists(config.explorationLimit()) - or - partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap, config) and - distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() - } or - TPartialPathNodeRev( - NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, - RevPartialAccessPath ap, Configuration config - ) { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() and - exists(config.explorationLimit()) - or - revPartialPathStep(_, node, state, sc1, sc2, sc3, ap, config) and - not clearsContentEx(node, ap.getHead()) and + override int length() { result = len } + + override string toString() { + if len = 1 + then result = "[" + head.toString() + "]" + else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" + } + } + + abstract private class PathNodeImpl extends TPathNode { + /** Gets the `FlowState` of this node. */ + abstract FlowState getState(); + + /** Holds if this node is a source. */ + abstract predicate isSource(); + + abstract PathNodeImpl getASuccessorImpl(); + + private PathNodeImpl getASuccessorIfHidden() { + this.isHidden() and + result = this.getASuccessorImpl() + } + + pragma[nomagic] + private PathNodeImpl getANonHiddenSuccessor0() { + result = this.getASuccessorIfHidden*() and + not result.isHidden() + } + + final PathNodeImpl getANonHiddenSuccessor() { + result = this.getASuccessorImpl().getANonHiddenSuccessor0() and + not this.isHidden() + } + + abstract NodeEx getNodeEx(); + + predicate isHidden() { + not Config::includeHiddenNodes() and ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead()) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() + hiddenNode(this.getNodeEx().asNode()) and + not this.isSource() and + not this instanceof PathNodeSink + or + this.getNodeEx() instanceof TNodeImplicitRead + ) } - pragma[nomagic] - private predicate partialPathNodeMk0( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap, config) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - not clearsContentEx(node, ap.getHead().getContent()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead().getContent()) - ) and - if node.asNode() instanceof CastingNode - then compatibleTypes(node.getDataFlowType(), ap.getType()) - else any() - } + string getSourceGroup() { + this.isSource() and + Config::sourceGrouping(this.getNodeEx().asNode(), result) + } + + predicate isFlowSource() { + this.isSource() and not exists(this.getSourceGroup()) + or + this instanceof PathNodeSourceGroup + } + + predicate isFlowSink() { + this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or + this instanceof PathNodeSinkGroup + } + + private string ppAp() { + this instanceof PathNodeSink and result = "" + or + exists(string s | s = this.(PathNodeMid).getAp().toString() | + if s = "" then result = "" else result = " " + s + ) + } + + private string ppCtx() { + this instanceof PathNodeSink and result = "" + or + result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + } - /** - * A `Node` augmented with a call context, an access path, and a configuration. - */ - class PartialPathNode extends TPartialPathNode { /** Gets a textual representation of this element. */ string toString() { result = this.getNodeEx().toString() + this.ppAp() } @@ -4341,305 +3073,339 @@ private module FlowExploration { ) { this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) } + } + + /** Holds if `n` can reach a sink. */ + private predicate directReach(PathNodeImpl n) { + n instanceof PathNodeSink or + n instanceof PathNodeSinkGroup or + directReach(n.getANonHiddenSuccessor()) + } + + /** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ + private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } + + /** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ + private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { + n1.getANonHiddenSuccessor() = n2 and directReach(n2) + } + + private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) + + /** + * A `Node` augmented with a call context (except for sinks) and an access path. + * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. + */ + class PathNode instanceof PathNodeImpl { + PathNode() { reach(this) } + + /** Gets a textual representation of this element. */ + final string toString() { result = super.toString() } + + /** + * Gets a textual representation of this element, including a textual + * representation of the call context. + */ + final string toStringWithContext() { result = super.toStringWithContext() } + + /** + * 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/). + */ + final predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } /** Gets the underlying `Node`. */ - final Node getNode() { this.getNodeEx().projectToNode() = result } + final Node getNode() { super.getNodeEx().projectToNode() = result } - FlowState getState() { none() } - - private NodeEx getNodeEx() { - result = this.(PartialPathNodeFwd).getNodeEx() or - result = this.(PartialPathNodeRev).getNodeEx() - } - - /** Gets the associated configuration. */ - Configuration getConfiguration() { none() } + /** Gets the `FlowState` of this node. */ + final FlowState getState() { result = super.getState() } /** Gets a successor of this node, if any. */ - PartialPathNode getASuccessor() { none() } + final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } - /** - * Gets the approximate distance to the nearest source measured in number - * of interprocedural steps. - */ - int getSourceDistance() { - result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } + /** Holds if this node is a source. */ + final predicate isSource() { super.isSource() } - /** - * Gets the approximate distance to the nearest sink measured in number - * of interprocedural steps. - */ - int getSinkDistance() { - result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } + /** Holds if this node is a grouping of source nodes. */ + final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group) } - private string ppAp() { - exists(string s | - s = this.(PartialPathNodeFwd).getAp().toString() or - s = this.(PartialPathNodeRev).getAp().toString() - | - if s = "" then result = "" else result = " " + s - ) - } - - private string ppCtx() { - result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" - } - - /** Holds if this is a source in a forward-flow path. */ - predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } - - /** Holds if this is a sink in a reverse-flow path. */ - predicate isRevSink() { this.(PartialPathNodeRev).isSink() } + /** Holds if this node is a grouping of sink nodes. */ + final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group) } } /** * Provides the query predicates needed to include a graph in a path-problem query. */ - module PartialPathGraph { + module PathGraph { /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } + query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } + + /** Holds if `n` is a node in the graph of data flow path explanations. */ + query predicate nodes(PathNode n, string key, string val) { + key = "semmle.label" and val = n.toString() + } + + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through + * a subpath between `par` and `ret` with the connecting edges `arg -> par` and + * `ret -> out` is summarized as the edge `arg -> out`. + */ + query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { + Subpaths::subpaths(arg, par, ret, out) + } } - private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { + /** + * An intermediate flow graph node. This is a tuple consisting of a `Node`, + * a `FlowState`, a `CallContext`, a `SummaryCtx`, and an `AccessPath`. + */ + private class PathNodeMid extends PathNodeImpl, TPathNodeMid { NodeEx node; FlowState state; CallContext cc; - TSummaryCtx1 sc1; - TSummaryCtx2 sc2; - TSummaryCtx3 sc3; - PartialAccessPath ap; - Configuration config; + SummaryCtx sc; + AccessPath ap; - PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap, config) } + PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap) } - NodeEx getNodeEx() { result = node } + override NodeEx getNodeEx() { result = node } override FlowState getState() { result = state } CallContext getCallContext() { result = cc } - TSummaryCtx1 getSummaryCtx1() { result = sc1 } + SummaryCtx getSummaryCtx() { result = sc } - TSummaryCtx2 getSummaryCtx2() { result = sc2 } + AccessPath getAp() { result = ap } - TSummaryCtx3 getSummaryCtx3() { result = sc3 } - - PartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeFwd getASuccessor() { - partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp(), - result.getConfiguration()) + private PathNodeMid getSuccMid() { + pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), + result.getSummaryCtx(), result.getAp()) } - predicate isSource() { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap instanceof TPartialNil + override PathNodeImpl getASuccessorImpl() { + // an intermediate step to another intermediate node + result = this.getSuccMid() + or + // a final step to a sink + result = this.getSuccMid().projectToSink() + } + + override predicate isSource() { + sourceNode(node, state) and + ( + if hasSourceCallCtx() + then cc instanceof CallContextSomeCall + else cc instanceof CallContextAny + ) and + sc instanceof SummaryCtxNone and + ap = TAccessPathNil(node.getDataFlowType()) + } + + predicate isAtSink() { + sinkNode(node, state) and + ap instanceof AccessPathNil and + if hasSinkCallCtx() + then + // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` + // is exactly what we need to check. This also implies + // `sc instanceof SummaryCtxNone`. + // For `FeatureEqualSourceSinkCallContext` the initial call context was + // set to `CallContextSomeCall` and jumps are disallowed, so + // `cc instanceof CallContextNoCall` never holds. On the other hand, + // in this case there's never any need to enter a call except to identify + // a summary, so the condition in `pathIntoCallable` enforces this, which + // means that `sc instanceof SummaryCtxNone` holds if and only if we are + // in the call context of the source. + sc instanceof SummaryCtxNone or + cc instanceof CallContextNoCall + else any() + } + + PathNodeSink projectToSink() { + this.isAtSink() and + result.getNodeEx() = node and + result.getState() = state } } - private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { + /** + * A flow graph node corresponding to a sink. This is disjoint from the + * intermediate nodes in order to uniquely correspond to a given sink by + * excluding the `CallContext`. + */ + private class PathNodeSink extends PathNodeImpl, TPathNodeSink { NodeEx node; FlowState state; - TRevSummaryCtx1 sc1; - TRevSummaryCtx2 sc2; - TRevSummaryCtx3 sc3; - RevPartialAccessPath ap; - Configuration config; - PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap, config) } + PathNodeSink() { this = TPathNodeSink(node, state) } - NodeEx getNodeEx() { result = node } + override NodeEx getNodeEx() { result = node } override FlowState getState() { result = state } - TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } + override PathNodeImpl getASuccessorImpl() { result = TPathNodeSinkGroup(this.getSinkGroup()) } - TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } + override predicate isSource() { sourceNode(node, state) } - TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } + string getSinkGroup() { Config::sinkGrouping(node.asNode(), result) } + } - RevPartialAccessPath getAp() { result = ap } + private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { + string sourceGroup; - override Configuration getConfiguration() { result = config } + PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup) } - override PartialPathNodeRev getASuccessor() { - revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), - this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp(), this.getConfiguration()) - } + override NodeEx getNodeEx() { none() } - predicate isSink() { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() + override FlowState getState() { none() } + + override PathNodeImpl getASuccessorImpl() { result.getSourceGroup() = sourceGroup } + + override predicate isSource() { none() } + + override string toString() { result = sourceGroup } + + override 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 } } - private predicate partialPathStep( - PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, - TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config + private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { + string sinkGroup; + + PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup) } + + override NodeEx getNodeEx() { none() } + + override FlowState getState() { none() } + + override PathNodeImpl getASuccessorImpl() { none() } + + override predicate isSource() { none() } + + override string toString() { result = sinkGroup } + + override 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 + } + } + + private predicate pathNode( + PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, + LocalCallContext localCC ) { - not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and - ( - localFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - ) - or - jumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - partialPathStoreStep(mid, _, _, node, ap) and + midnode = mid.getNodeEx() and state = mid.getState() and cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(PartialAccessPath ap0, TypedContent tc | - partialPathReadStep(mid, ap0, tc, node, cc, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsFwd(ap, tc, ap0, config) - ) - or - partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap, config) - or - partialPathOutOfCallable(mid, node, state, cc, ap, config) and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() - or - partialPathThroughCallable(mid, node, state, cc, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() + sc = mid.getSummaryCtx() and + localCC = + getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), + midnode.getEnclosingCallable()) and + ap = mid.getAp() } - bindingset[result, i] - private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } - - pragma[inline] - private predicate partialPathStoreStep( - PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, - PartialAccessPath ap2 + /** + * Holds if data may flow from `mid` to `node`. The last step in or out of + * a callable is recorded by `cc`. + */ + pragma[assume_small_delta] + pragma[nomagic] + private predicate pathStep( + PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap ) { - exists(NodeEx midNode, DataFlowType contentType | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - store(midNode, tc, node, contentType, mid.getConfiguration()) and - ap2.getHead() = tc and - ap2.len() = unbindInt(ap1.len() + 1) and - compatibleTypes(ap1.getType(), contentType) + exists(NodeEx midnode, FlowState state0, LocalCallContext localCC | + pathNode(mid, midnode, state0, cc, sc, ap, localCC) and + localFlowBigStep(midnode, state0, node, state, true, _, localCC) ) + or + exists(AccessPath ap0, NodeEx midnode, FlowState state0, LocalCallContext localCC | + pathNode(mid, midnode, state0, cc, sc, ap0, localCC) and + localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), localCC) and + ap0 instanceof AccessPathNil + ) + or + jumpStepEx(mid.getNodeEx(), node) and + state = mid.getState() and + cc instanceof CallContextAny and + sc instanceof SummaryCtxNone and + ap = mid.getAp() + or + additionalJumpStep(mid.getNodeEx(), node) and + state = mid.getState() and + cc instanceof CallContextAny and + sc instanceof SummaryCtxNone and + mid.getAp() instanceof AccessPathNil and + ap = TAccessPathNil(node.getDataFlowType()) + or + additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state) and + cc instanceof CallContextAny and + sc instanceof SummaryCtxNone and + mid.getAp() instanceof AccessPathNil and + ap = TAccessPathNil(node.getDataFlowType()) + or + exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and + sc = mid.getSummaryCtx() + or + exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and + sc = mid.getSummaryCtx() + or + pathIntoCallable(mid, node, state, _, cc, sc, _) and ap = mid.getAp() + or + pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone + or + pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() } pragma[nomagic] - private predicate apConsFwd( - PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config + private predicate pathReadStep( + PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc ) { - exists(PartialPathNodeFwd mid | - partialPathStoreStep(mid, ap1, tc, _, ap2) and - config = mid.getConfiguration() - ) + ap0 = mid.getAp() and + tc = ap0.getHead() and + Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node) and + state = mid.getState() and + cc = mid.getCallContext() } pragma[nomagic] - private predicate partialPathReadStep( - PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc, - Configuration config + private predicate pathStoreStep( + PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and - ap.getHead() = tc and - pragma[only_bind_into](config) = mid.getConfiguration() and - cc = mid.getCallContext() - ) + ap0 = mid.getAp() and + Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _) and + state = mid.getState() and + cc = mid.getCallContext() } - private predicate partialPathOutOfCallable0( - PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, - PartialAccessPath ap, Configuration config + private predicate pathOutOfCallable0( + PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa ) { pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and state = mid.getState() and innercc = mid.getCallContext() and innercc instanceof CallContextNoCall and - ap = mid.getAp() and - config = mid.getConfiguration() + apa = mid.getAp().getApprox() } pragma[nomagic] - private predicate partialPathOutOfCallable1( - PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config + private predicate pathOutOfCallable1( + PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, + AccessPathApprox apa ) { exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - partialPathOutOfCallable0(mid, pos, state, innercc, ap, config) and + pathOutOfCallable0(mid, pos, state, innercc, apa) and c = pos.getCallable() and kind = pos.getKind() and resolveReturn(innercc, c, call) @@ -4648,53 +3414,86 @@ private module FlowExploration { ) } - private predicate partialPathOutOfCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(ReturnKindExt kind, DataFlowCall call | - partialPathOutOfCallable1(mid, call, kind, state, cc, ap, config) - | - out.asNode() = kind.getAnOutNode(call) + pragma[noinline] + private NodeEx getAnOutNodeFlow(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa) { + result.asNode() = kind.getAnOutNode(call) and + Stage5::revFlow(result, _, apa) + } + + /** + * Holds if data may flow from `mid` to `out`. The last step of this path + * is a return from a callable and is recorded by `cc`, if needed. + */ + pragma[noinline] + private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { + exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa | + pathOutOfCallable1(mid, call, kind, state, cc, apa) and + out = getAnOutNodeFlow(kind, call, apa) ) } + /** + * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. + */ pragma[noinline] - private predicate partialPathIntoArg( - PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, - DataFlowCall call, PartialAccessPath ap, Configuration config + private predicate pathIntoArg( + PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, + AccessPath ap, AccessPathApprox apa ) { - exists(ArgNode arg, ArgumentPosition apos | - arg = mid.getNodeEx().asNode() and - state = mid.getState() and - cc = mid.getCallContext() and - arg.argumentOf(call, apos) and - ap = mid.getAp() and - config = mid.getConfiguration() and + exists(ArgNodeEx arg, ArgumentPosition apos | + pathNode(mid, arg, state, cc, _, ap, _) and + arg.asNode().(ArgNode).argumentOf(call, apos) and + apa = ap.getApprox() and parameterMatch(ppos, apos) ) } pragma[nomagic] - private predicate partialPathIntoCallable0( - PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, PartialAccessPath ap, Configuration config + private predicate parameterCand( + DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa ) { - partialPathIntoArg(mid, pos, state, outercc, call, ap, config) and - callable = resolveCall(call, outercc) + exists(ParamNodeEx p | + Stage5::revFlow(p, _, apa) and + p.isParameterOf(callable, pos) + ) } - private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, - CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, - DataFlowCall call, PartialAccessPath ap, Configuration config + pragma[nomagic] + private predicate pathIntoCallable0( + PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, + CallContext outercc, DataFlowCall call, AccessPath ap ) { - exists(ParameterPosition pos, DataFlowCallable callable | - partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and + exists(AccessPathApprox apa | + pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, + pragma[only_bind_into](apa)) and + callable = resolveCall(call, outercc) and + parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa)) + ) + } + + /** + * Holds if data may flow from `mid` to `p` through `call`. The contexts + * before and after entering the callable are `outercc` and `innercc`, + * respectively. + */ + pragma[nomagic] + private predicate pathIntoCallable( + PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, + SummaryCtx sc, DataFlowCall call + ) { + exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | + pathIntoCallable0(mid, callable, pos, state, outercc, call, ap) and p.isParameterOf(callable, pos) and - sc1 = TSummaryCtx1Param(p) and - sc2 = TSummaryCtx2Some(state) and - sc3 = TSummaryCtx3Some(ap) + ( + sc = TSummaryCtxSome(p, state, ap) + or + not exists(TSummaryCtxSome(p, state, ap)) and + sc = TSummaryCtxNone() and + // When the call contexts of source and sink needs to match then there's + // never any reason to enter a callable except to find a summary. See also + // the comment in `PathNodeMid::isAtSink`. + not Config::getAFeature() instanceof FeatureEqualSourceSinkCallContext + ) | if recordDataFlowCallSite(call, callable) then innercc = TSpecificCall(call) @@ -4702,252 +3501,1108 @@ private module FlowExploration { ) } + /** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ pragma[nomagic] - private predicate paramFlowsThroughInPartialPath( - ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config + private predicate paramFlowsThrough( + ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, + AccessPathApprox apa ) { - exists(PartialPathNodeFwd mid, RetNodeEx ret | - mid.getNodeEx() = ret and + exists(RetNodeEx ret | + pathNode(_, ret, state, cc, sc, ap, _) and kind = ret.getKind() and + apa = ap.getApprox() and + parameterFlowThroughAllowed(sc.getParamNode(), kind) + ) + } + + pragma[assume_small_delta] + pragma[nomagic] + private predicate pathThroughCallable0( + DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, + AccessPath ap, AccessPathApprox apa + ) { + exists(CallContext innercc, SummaryCtx sc | + pathIntoCallable(mid, _, _, cc, innercc, sc, call) and + paramFlowsThrough(kind, state, innercc, sc, ap, apa) + ) + } + + /** + * Holds if data may flow from `mid` through a callable to the node `out`. + * The context `cc` is restored to its value prior to entering the callable. + */ + pragma[noinline] + private predicate pathThroughCallable( + PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap + ) { + exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa | + pathThroughCallable0(call, mid, kind, state, cc, ap, apa) and + out = getAnOutNodeFlow(kind, call, apa) + ) + } + + private module Subpaths { + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by + * `kind`, `sc`, `apout`, and `innercc`. + */ + pragma[nomagic] + private predicate subpaths01( + PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, + NodeEx out, FlowState sout, AccessPath apout + ) { + pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and + pathIntoCallable(arg, par, _, _, innercc, sc, _) and + paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, + pragma[only_bind_into](apout), _) and + not arg.isHidden() + } + + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by + * `kind`, `sc`, `sout`, `apout`, and `innercc`. + */ + pragma[nomagic] + private predicate subpaths02( + PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, + NodeEx out, FlowState sout, AccessPath apout + ) { + subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and + out.asNode() = kind.getAnOutNode(_) + } + + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple. + */ + pragma[nomagic] + private predicate subpaths03( + PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, + AccessPath apout + ) { + exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | + subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and + pathNode(ret, retnode, sout, innercc, sc, apout, _) and + kind = retnode.getKind() + ) + } + + private PathNodeImpl localStepToHidden(PathNodeImpl n) { + n.getASuccessorImpl() = result and + result.isHidden() and + exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | + localFlowBigStep(n1, _, n2, _, _, _, _) or + storeEx(n1, _, n2, _) or + readSetEx(n1, _, n2) + ) + } + + pragma[nomagic] + private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { + succ = pred.getANonHiddenSuccessor() and + succNode = succ.getNodeEx() + } + + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through + * a subpath between `par` and `ret` with the connecting edges `arg -> par` and + * `ret -> out` is summarized as the edge `arg -> out`. + */ + predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { + exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | + pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and + subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and + hasSuccessor(pragma[only_bind_into](arg), par, p) and + not ret.isHidden() and + pathNode(out0, o, sout, _, _, apout, _) + | + out = out0 or out = out0.projectToSink() + ) + } + + /** + * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. + */ + predicate retReach(PathNodeImpl n) { + exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) + or + exists(PathNodeImpl mid | + retReach(mid) and + n.getANonHiddenSuccessor() = mid and + not subpaths(_, mid, _, _) + ) + } + } + + /** + * Holds if data can flow from `source` to `sink`. + * + * The corresponding paths are generated from the end-points and the graph + * included in the module `PathGraph`. + */ + predicate hasFlowPath(PathNode source, PathNode sink) { + exists(PathNodeImpl flowsource, PathNodeImpl flowsink | + source = flowsource and sink = flowsink + | + flowsource.isFlowSource() and + (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and + flowsink.isFlowSink() + ) + } + + private predicate flowsTo(PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink) { + flowsource.isSource() and + flowsource.getNodeEx().asNode() = source and + (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and + flowsink.getNodeEx().asNode() = sink + } + + /** + * Holds if data can flow from `source` to `sink`. + */ + predicate hasFlow(Node source, Node sink) { flowsTo(_, _, source, sink) } + + /** + * Holds if data can flow from some source to `sink`. + */ + predicate hasFlowTo(Node sink) { sink = any(PathNodeSink n).getNodeEx().asNode() } + + /** + * Holds if data can flow from some source to `sink`. + */ + predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) } + + private predicate finalStats( + boolean fwd, int nodes, int fields, int conscand, int states, int tuples + ) { + fwd = true and + nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and + fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and + conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and + states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and + tuples = count(PathNodeImpl pn) + or + fwd = false and + nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and + fields = + count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and + conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and + states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and + tuples = count(PathNode pn) + } + + /** + * INTERNAL: Only for debugging. + * + * Calculates per-stage metrics for data flow. + */ + predicate stageStats( + int n, string stage, int nodes, int fields, int conscand, int states, int tuples + ) { + stage = "1 Fwd" and + n = 10 and + Stage1::stats(true, nodes, fields, conscand, states, tuples) + or + stage = "1 Rev" and + n = 15 and + Stage1::stats(false, nodes, fields, conscand, states, tuples) + or + stage = "2 Fwd" and + n = 20 and + Stage2::stats(true, nodes, fields, conscand, states, tuples) + or + stage = "2 Rev" and + n = 25 and + Stage2::stats(false, nodes, fields, conscand, states, tuples) + or + stage = "3 Fwd" and + n = 30 and + Stage3::stats(true, nodes, fields, conscand, states, tuples) + or + stage = "3 Rev" and + n = 35 and + Stage3::stats(false, nodes, fields, conscand, states, tuples) + or + stage = "4 Fwd" and + n = 40 and + Stage4::stats(true, nodes, fields, conscand, states, tuples) + or + stage = "4 Rev" and + n = 45 and + Stage4::stats(false, nodes, fields, conscand, states, tuples) + or + stage = "5 Fwd" and + n = 50 and + Stage5::stats(true, nodes, fields, conscand, states, tuples) + or + stage = "5 Rev" and + n = 55 and + Stage5::stats(false, nodes, fields, conscand, states, tuples) + or + stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) + or + stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) + } + + module FlowExploration { + private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2) { + exists(NodeEx node1, NodeEx node2 | + jumpStepEx(node1, node2) + or + additionalJumpStep(node1, node2) + or + additionalJumpStateStep(node1, _, node2, _) + or + // flow into callable + viableParamArgEx(_, node2, node1) + or + // flow out of a callable + viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) + | + c1 = node1.getEnclosingCallable() and + c2 = node2.getEnclosingCallable() and + c1 != c2 + ) + } + + private predicate interestingCallableSrc(DataFlowCallable c) { + exists(Node n | Config::isSource(n, _) and c = getNodeEnclosingCallable(n)) + or + exists(DataFlowCallable mid | interestingCallableSrc(mid) and callableStep(mid, c)) + } + + private predicate interestingCallableSink(DataFlowCallable c) { + exists(Node n | Config::isSink(n, _) and c = getNodeEnclosingCallable(n)) + or + exists(DataFlowCallable mid | interestingCallableSink(mid) and callableStep(c, mid)) + } + + private newtype TCallableExt = + TCallable(DataFlowCallable c) { + interestingCallableSrc(c) or + interestingCallableSink(c) + } or + TCallableSrc() or + TCallableSink() + + private predicate callableExtSrc(TCallableSrc src) { any() } + + private predicate callableExtSink(TCallableSink sink) { any() } + + private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { + exists(DataFlowCallable c1, DataFlowCallable c2 | + callableStep(c1, c2) and + ce1 = TCallable(c1) and + ce2 = TCallable(c2) + ) + or + exists(Node n | + ce1 = TCallableSrc() and + Config::isSource(n, _) and + ce2 = TCallable(getNodeEnclosingCallable(n)) + ) + or + exists(Node n | + ce2 = TCallableSink() and + Config::isSink(n, _) and + ce1 = TCallable(getNodeEnclosingCallable(n)) + ) + } + + private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { + callableExtStepFwd(ce2, ce1) + } + + private int distSrcExt(TCallableExt c) = + shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) + + private int distSinkExt(TCallableExt c) = + shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) + + private int distSrc(DataFlowCallable c) { result = distSrcExt(TCallable(c)) - 1 } + + private int distSink(DataFlowCallable c) { result = distSinkExt(TCallable(c)) - 1 } + + private newtype TPartialAccessPath = + TPartialNil(DataFlowType t) or + TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } + + /** + * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first + * element of the list and its length are tracked. If data flows from a source to + * a given node with a given `AccessPath`, this indicates the sequence of + * dereference operations needed to get from the value in the node to the + * tracked object. The final type indicates the type of the tracked object. + */ + private class PartialAccessPath extends TPartialAccessPath { + abstract string toString(); + + TypedContent getHead() { this = TPartialCons(result, _) } + + int len() { + this = TPartialNil(_) and result = 0 + or + this = TPartialCons(_, result) + } + + DataFlowType getType() { + this = TPartialNil(result) + or + exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) + } + } + + private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { + override string toString() { + exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) + } + } + + private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { + override string toString() { + exists(TypedContent tc, int len | this = TPartialCons(tc, len) | + if len = 1 + then result = "[" + tc.toString() + "]" + else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" + ) + } + } + + private newtype TRevPartialAccessPath = + TRevPartialNil() or + TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } + + /** + * Conceptually a list of `Content`s, but only the first + * element of the list and its length are tracked. + */ + private class RevPartialAccessPath extends TRevPartialAccessPath { + abstract string toString(); + + Content getHead() { this = TRevPartialCons(result, _) } + + int len() { + this = TRevPartialNil() and result = 0 + or + this = TRevPartialCons(_, result) + } + } + + private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { + override string toString() { result = "" } + } + + private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { + override string toString() { + exists(Content c, int len | this = TRevPartialCons(c, len) | + if len = 1 + then result = "[" + c.toString() + "]" + else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" + ) + } + } + + private predicate relevantState(FlowState state) { + sourceNode(_, state) or + sinkNode(_, state) or + additionalLocalStateStep(_, state, _, _) or + additionalLocalStateStep(_, _, _, state) or + additionalJumpStateStep(_, state, _, _) or + additionalJumpStateStep(_, _, _, state) + } + + private newtype TSummaryCtx1 = + TSummaryCtx1None() or + TSummaryCtx1Param(ParamNodeEx p) + + private newtype TSummaryCtx2 = + TSummaryCtx2None() or + TSummaryCtx2Some(FlowState s) { relevantState(s) } + + private newtype TSummaryCtx3 = + TSummaryCtx3None() or + TSummaryCtx3Some(PartialAccessPath ap) + + private newtype TRevSummaryCtx1 = + TRevSummaryCtx1None() or + TRevSummaryCtx1Some(ReturnPosition pos) + + private newtype TRevSummaryCtx2 = + TRevSummaryCtx2None() or + TRevSummaryCtx2Some(FlowState s) { relevantState(s) } + + private newtype TRevSummaryCtx3 = + TRevSummaryCtx3None() or + TRevSummaryCtx3Some(RevPartialAccessPath ap) + + private newtype TPartialPathNode = + TPartialPathNodeFwd( + NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, + TSummaryCtx3 sc3, PartialAccessPath ap + ) { + sourceNode(node, state) and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() and + ap = TPartialNil(node.getDataFlowType()) and + exists(explorationLimit()) + or + partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap) and + distSrc(node.getEnclosingCallable()) <= explorationLimit() + } or + TPartialPathNodeRev( + NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, + RevPartialAccessPath ap + ) { + sinkNode(node, state) and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + sc3 = TRevSummaryCtx3None() and + ap = TRevPartialNil() and + exists(explorationLimit()) + or + revPartialPathStep(_, node, state, sc1, sc2, sc3, ap) and + not clearsContentEx(node, ap.getHead()) and + ( + notExpectsContent(node) or + expectsContentEx(node, ap.getHead()) + ) and + not fullBarrier(node) and + not stateBarrier(node, state) and + distSink(node.getEnclosingCallable()) <= explorationLimit() + } + + pragma[nomagic] + private predicate partialPathNodeMk0( + NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, + TSummaryCtx3 sc3, PartialAccessPath ap + ) { + partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap) and + not fullBarrier(node) and + not stateBarrier(node, state) and + not clearsContentEx(node, ap.getHead().getContent()) and + ( + notExpectsContent(node) or + expectsContentEx(node, ap.getHead().getContent()) + ) and + if node.asNode() instanceof CastingNode + then compatibleTypes(node.getDataFlowType(), ap.getType()) + else any() + } + + /** + * A `Node` augmented with a call context, an access path, and a configuration. + */ + class PartialPathNode extends TPartialPathNode { + /** Gets a textual representation of this element. */ + string toString() { result = this.getNodeEx().toString() + this.ppAp() } + + /** + * Gets a textual representation of this element, including a textual + * representation of the call context. + */ + string toStringWithContext() { + result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() + } + + /** + * 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 + ) { + this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + + /** Gets the underlying `Node`. */ + final Node getNode() { this.getNodeEx().projectToNode() = result } + + FlowState getState() { none() } + + private NodeEx getNodeEx() { + result = this.(PartialPathNodeFwd).getNodeEx() or + result = this.(PartialPathNodeRev).getNodeEx() + } + + /** Gets a successor of this node, if any. */ + PartialPathNode getASuccessor() { none() } + + /** + * Gets the approximate distance to the nearest source measured in number + * of interprocedural steps. + */ + int getSourceDistance() { result = distSrc(this.getNodeEx().getEnclosingCallable()) } + + /** + * Gets the approximate distance to the nearest sink measured in number + * of interprocedural steps. + */ + int getSinkDistance() { result = distSink(this.getNodeEx().getEnclosingCallable()) } + + private string ppAp() { + exists(string s | + s = this.(PartialPathNodeFwd).getAp().toString() or + s = this.(PartialPathNodeRev).getAp().toString() + | + if s = "" then result = "" else result = " " + s + ) + } + + private string ppCtx() { + result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" + } + + /** Holds if this is a source in a forward-flow path. */ + predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } + + /** Holds if this is a sink in a reverse-flow path. */ + predicate isRevSink() { this.(PartialPathNodeRev).isSink() } + } + + /** + * Provides the query predicates needed to include a graph in a path-problem query. + */ + module PartialPathGraph { + /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ + query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } + } + + private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { + NodeEx node; + FlowState state; + CallContext cc; + TSummaryCtx1 sc1; + TSummaryCtx2 sc2; + TSummaryCtx3 sc3; + PartialAccessPath ap; + + PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap) } + + NodeEx getNodeEx() { result = node } + + override FlowState getState() { result = state } + + CallContext getCallContext() { result = cc } + + TSummaryCtx1 getSummaryCtx1() { result = sc1 } + + TSummaryCtx2 getSummaryCtx2() { result = sc2 } + + TSummaryCtx3 getSummaryCtx3() { result = sc3 } + + PartialAccessPath getAp() { result = ap } + + override PartialPathNodeFwd getASuccessor() { + partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), + result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp()) + } + + predicate isSource() { + sourceNode(node, state) and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() and + ap instanceof TPartialNil + } + } + + private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { + NodeEx node; + FlowState state; + TRevSummaryCtx1 sc1; + TRevSummaryCtx2 sc2; + TRevSummaryCtx3 sc3; + RevPartialAccessPath ap; + + PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap) } + + NodeEx getNodeEx() { result = node } + + override FlowState getState() { result = state } + + TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } + + TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } + + TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } + + RevPartialAccessPath getAp() { result = ap } + + override PartialPathNodeRev getASuccessor() { + revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), + this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp()) + } + + predicate isSink() { + sinkNode(node, state) and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + sc3 = TRevSummaryCtx3None() and + ap = TRevPartialNil() + } + } + + private predicate partialPathStep( + PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, + TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap + ) { + not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and + ( + localFlowStepEx(mid.getNodeEx(), node) and + state = mid.getState() and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + ap = mid.getAp() + or + additionalLocalFlowStep(mid.getNodeEx(), node) and + state = mid.getState() and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + mid.getAp() instanceof PartialAccessPathNil and + ap = TPartialNil(node.getDataFlowType()) + or + additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state) and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + mid.getAp() instanceof PartialAccessPathNil and + ap = TPartialNil(node.getDataFlowType()) + ) + or + jumpStepEx(mid.getNodeEx(), node) and + state = mid.getState() and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() and + ap = mid.getAp() + or + additionalJumpStep(mid.getNodeEx(), node) and + state = mid.getState() and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() and + mid.getAp() instanceof PartialAccessPathNil and + ap = TPartialNil(node.getDataFlowType()) + or + additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state) and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() and + mid.getAp() instanceof PartialAccessPathNil and + ap = TPartialNil(node.getDataFlowType()) + or + partialPathStoreStep(mid, _, _, node, ap) and state = mid.getState() and cc = mid.getCallContext() and sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() and + sc3 = mid.getSummaryCtx3() + or + exists(PartialAccessPath ap0, TypedContent tc | + partialPathReadStep(mid, ap0, tc, node, cc) and + state = mid.getState() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + apConsFwd(ap, tc, ap0) + ) + or + partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap) + or + partialPathOutOfCallable(mid, node, state, cc, ap) and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() + or + partialPathThroughCallable(mid, node, state, cc, ap) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() + } + + bindingset[result, i] + private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } + + pragma[inline] + private predicate partialPathStoreStep( + PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, + PartialAccessPath ap2 + ) { + exists(NodeEx midNode, DataFlowType contentType | + midNode = mid.getNodeEx() and + ap1 = mid.getAp() and + storeEx(midNode, tc, node, contentType) and + ap2.getHead() = tc and + ap2.len() = unbindInt(ap1.len() + 1) and + compatibleTypes(ap1.getType(), contentType) + ) + } + + pragma[nomagic] + private predicate apConsFwd(PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2) { + partialPathStoreStep(_, ap1, tc, _, ap2) + } + + pragma[nomagic] + private predicate partialPathReadStep( + PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc + ) { + exists(NodeEx midNode | + midNode = mid.getNodeEx() and + ap = mid.getAp() and + read(midNode, tc.getContent(), node) and + ap.getHead() = tc and + cc = mid.getCallContext() + ) + } + + private predicate partialPathOutOfCallable0( + PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, + PartialAccessPath ap + ) { + pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and + state = mid.getState() and + innercc = mid.getCallContext() and + innercc instanceof CallContextNoCall and ap = mid.getAp() - ) - } + } - pragma[noinline] - private predicate partialPathThroughCallable0( - DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | - partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _, config) and - paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap, config) - ) - } + pragma[nomagic] + private predicate partialPathOutOfCallable1( + PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, + CallContext cc, PartialAccessPath ap + ) { + exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | + partialPathOutOfCallable0(mid, pos, state, innercc, ap) and + c = pos.getCallable() and + kind = pos.getKind() and + resolveReturn(innercc, c, call) + | + if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + ) + } - private predicate partialPathThroughCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ReturnKindExt kind | - partialPathThroughCallable0(call, mid, kind, state, cc, ap, config) and - out.asNode() = kind.getAnOutNode(call) - ) - } + private predicate partialPathOutOfCallable( + PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap + ) { + exists(ReturnKindExt kind, DataFlowCall call | + partialPathOutOfCallable1(mid, call, kind, state, cc, ap) + | + out.asNode() = kind.getAnOutNode(call) + ) + } - pragma[nomagic] - private predicate revPartialPathStep( - PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, - TRevSummaryCtx3 sc3, RevPartialAccessPath ap, Configuration config - ) { - localFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - jumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - revPartialPathReadStep(mid, _, _, node, ap) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(RevPartialAccessPath ap0, Content c | - revPartialPathStoreStep(mid, ap0, c, node, config) and + pragma[noinline] + private predicate partialPathIntoArg( + PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, + DataFlowCall call, PartialAccessPath ap + ) { + exists(ArgNode arg, ArgumentPosition apos | + arg = mid.getNodeEx().asNode() and + state = mid.getState() and + cc = mid.getCallContext() and + arg.argumentOf(call, apos) and + ap = mid.getAp() and + parameterMatch(ppos, apos) + ) + } + + pragma[nomagic] + private predicate partialPathIntoCallable0( + PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, + CallContext outercc, DataFlowCall call, PartialAccessPath ap + ) { + partialPathIntoArg(mid, pos, state, outercc, call, ap) and + callable = resolveCall(call, outercc) + } + + private predicate partialPathIntoCallable( + PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, + CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, + DataFlowCall call, PartialAccessPath ap + ) { + exists(ParameterPosition pos, DataFlowCallable callable | + partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap) and + p.isParameterOf(callable, pos) and + sc1 = TSummaryCtx1Param(p) and + sc2 = TSummaryCtx2Some(state) and + sc3 = TSummaryCtx3Some(ap) + | + if recordDataFlowCallSite(call, callable) + then innercc = TSpecificCall(call) + else innercc = TSomeCall() + ) + } + + pragma[nomagic] + private predicate paramFlowsThroughInPartialPath( + ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, + TSummaryCtx3 sc3, PartialAccessPath ap + ) { + exists(PartialPathNodeFwd mid, RetNodeEx ret | + mid.getNodeEx() = ret and + kind = ret.getKind() and + state = mid.getState() and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + ap = mid.getAp() + ) + } + + pragma[noinline] + private predicate partialPathThroughCallable0( + DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, + CallContext cc, PartialAccessPath ap + ) { + exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | + partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _) and + paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap) + ) + } + + private predicate partialPathThroughCallable( + PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap + ) { + exists(DataFlowCall call, ReturnKindExt kind | + partialPathThroughCallable0(call, mid, kind, state, cc, ap) and + out.asNode() = kind.getAnOutNode(call) + ) + } + + pragma[nomagic] + private predicate revPartialPathStep( + PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, + TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, RevPartialAccessPath ap + ) { + localFlowStepEx(node, mid.getNodeEx()) and state = mid.getState() and sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and sc3 = mid.getSummaryCtx3() and - apConsRev(ap, c, ap0, config) - ) - or - exists(ParamNodeEx p | - mid.getNodeEx() = p and - viableParamArgEx(_, p, node) and + ap = mid.getAp() + or + additionalLocalFlowStep(node, mid.getNodeEx()) and state = mid.getState() and sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and sc3 = mid.getSummaryCtx3() and + mid.getAp() instanceof RevPartialAccessPathNil and + ap = TRevPartialNil() + or + additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState()) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + mid.getAp() instanceof RevPartialAccessPathNil and + ap = TRevPartialNil() + or + jumpStepEx(node, mid.getNodeEx()) and + state = mid.getState() and sc1 = TRevSummaryCtx1None() and sc2 = TRevSummaryCtx2None() and sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - or - exists(ReturnPosition pos | - revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap, config) and - pos = getReturnPosition(node.asNode()) - ) - or - revPartialPathThroughCallable(mid, node, state, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - pragma[inline] - private predicate revPartialPathReadStep( - PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, - RevPartialAccessPath ap2 - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - read(node, c, midNode, mid.getConfiguration()) and - ap2.getHead() = c and - ap2.len() = unbindInt(ap1.len() + 1) - ) - } - - pragma[nomagic] - private predicate apConsRev( - RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeRev mid | - revPartialPathReadStep(mid, ap1, c, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathStoreStep( - PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config - ) { - exists(NodeEx midNode, TypedContent tc | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - store(node, tc, midNode, _, config) and - ap.getHead() = c and - config = mid.getConfiguration() and - tc.getContent() = c - ) - } - - pragma[nomagic] - private predicate revPartialPathIntoReturn( - PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, - TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap, - Configuration config - ) { - exists(NodeEx out | - mid.getNodeEx() = out and - mid.getState() = state and - viableReturnPosOutEx(call, pos, out) and - sc1 = TRevSummaryCtx1Some(pos) and - sc2 = TRevSummaryCtx2Some(state) and - sc3 = TRevSummaryCtx3Some(ap) and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathFlowsThrough( - ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, - TRevSummaryCtx3Some sc3, RevPartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | - mid.getNodeEx() = p and - mid.getState() = state and - p.getPosition() = ppos and + ap = mid.getAp() + or + additionalJumpStep(node, mid.getNodeEx()) and + state = mid.getState() and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + sc3 = TRevSummaryCtx3None() and + mid.getAp() instanceof RevPartialAccessPathNil and + ap = TRevPartialNil() + or + additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState()) and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + sc3 = TRevSummaryCtx3None() and + mid.getAp() instanceof RevPartialAccessPathNil and + ap = TRevPartialNil() + or + revPartialPathReadStep(mid, _, _, node, ap) and + state = mid.getState() and sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } + sc3 = mid.getSummaryCtx3() + or + exists(RevPartialAccessPath ap0, Content c | + revPartialPathStoreStep(mid, ap0, c, node) and + state = mid.getState() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + apConsRev(ap, c, ap0) + ) + or + exists(ParamNodeEx p | + mid.getNodeEx() = p and + viableParamArgEx(_, p, node) and + state = mid.getState() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + sc3 = TRevSummaryCtx3None() and + ap = mid.getAp() + ) + or + exists(ReturnPosition pos | + revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap) and + pos = getReturnPosition(node.asNode()) + ) + or + revPartialPathThroughCallable(mid, node, state, ap) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() + } - pragma[nomagic] - private predicate revPartialPathThroughCallable0( - DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, - RevPartialAccessPath ap, Configuration config - ) { - exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | - revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _, config) and - revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap, config) - ) - } + pragma[inline] + private predicate revPartialPathReadStep( + PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, + RevPartialAccessPath ap2 + ) { + exists(NodeEx midNode | + midNode = mid.getNodeEx() and + ap1 = mid.getAp() and + read(node, c, midNode) and + ap2.getHead() = c and + ap2.len() = unbindInt(ap1.len() + 1) + ) + } - pragma[nomagic] - private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ArgumentPosition pos | - revPartialPathThroughCallable0(call, mid, pos, state, ap, config) and - node.asNode().(ArgNode).argumentOf(call, pos) - ) + pragma[nomagic] + private predicate apConsRev(RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2) { + revPartialPathReadStep(_, ap1, c, _, ap2) + } + + pragma[nomagic] + private predicate revPartialPathStoreStep( + PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node + ) { + exists(NodeEx midNode, TypedContent tc | + midNode = mid.getNodeEx() and + ap = mid.getAp() and + storeEx(node, tc, midNode, _) and + ap.getHead() = c and + tc.getContent() = c + ) + } + + pragma[nomagic] + private predicate revPartialPathIntoReturn( + PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, + TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap + ) { + exists(NodeEx out | + mid.getNodeEx() = out and + mid.getState() = state and + viableReturnPosOutEx(call, pos, out) and + sc1 = TRevSummaryCtx1Some(pos) and + sc2 = TRevSummaryCtx2Some(state) and + sc3 = TRevSummaryCtx3Some(ap) and + ap = mid.getAp() + ) + } + + pragma[nomagic] + private predicate revPartialPathFlowsThrough( + ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, + TRevSummaryCtx3Some sc3, RevPartialAccessPath ap + ) { + exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | + mid.getNodeEx() = p and + mid.getState() = state and + p.getPosition() = ppos and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + ap = mid.getAp() and + parameterMatch(ppos, apos) + ) + } + + pragma[nomagic] + private predicate revPartialPathThroughCallable0( + DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, + RevPartialAccessPath ap + ) { + exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | + revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _) and + revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap) + ) + } + + pragma[nomagic] + private predicate revPartialPathThroughCallable( + PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap + ) { + exists(DataFlowCall call, ArgumentPosition pos | + revPartialPathThroughCallable0(call, mid, pos, state, ap) and + node.asNode().(ArgNode).argumentOf(call, pos) + ) + } + + private predicate partialFlow(PartialPathNode source, PartialPathNode node) { + source.isFwdSource() and + node = source.getASuccessor+() + } + + private predicate revPartialFlow(PartialPathNode node, PartialPathNode sink) { + sink.isRevSink() and + node.getASuccessor+() = sink + } + + /** + * Holds if there is a partial data flow path from `source` to `node`. The + * approximate distance between `node` and the closest source is `dist` and + * is restricted to be less than or equal to `explorationLimit()`. This + * predicate completely disregards sink definitions. + * + * This predicate is intended for data-flow exploration and debugging and may + * perform poorly if the number of sources is too big and/or the exploration + * limit is set too high without using barriers. + * + * To use this in a `path-problem` query, import the module `PartialPathGraph`. + */ + predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { + partialFlow(source, node) and + dist = node.getSourceDistance() + } + + /** + * Holds if there is a partial data flow path from `node` to `sink`. The + * approximate distance between `node` and the closest sink is `dist` and + * is restricted to be less than or equal to `explorationLimit()`. This + * predicate completely disregards source definitions. + * + * This predicate is intended for data-flow exploration and debugging and may + * perform poorly if the number of sinks is too big and/or the exploration + * limit is set too high without using barriers. + * + * To use this in a `path-problem` query, import the module `PartialPathGraph`. + * + * Note that reverse flow has slightly lower precision than the corresponding + * forward flow, as reverse flow disregards type pruning among other features. + */ + predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { + revPartialFlow(node, sink) and + dist = node.getSinkDistance() + } } } - -import FlowExploration - -private predicate partialFlow( - PartialPathNode source, PartialPathNode node, Configuration configuration -) { - source.getConfiguration() = configuration and - source.isFwdSource() and - node = source.getASuccessor+() -} - -private predicate revPartialFlow( - PartialPathNode node, PartialPathNode sink, Configuration configuration -) { - sink.getConfiguration() = configuration and - sink.isRevSink() and - node.getASuccessor+() = sink -} diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl1.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl1.qll new file mode 100644 index 00000000000..e6bdc74cceb --- /dev/null +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl1.qll @@ -0,0 +1,396 @@ +/** + * DEPRECATED: Use `Make` and `MakeWithState` instead. + * + * Provides a `Configuration` class backwards-compatible interface to the data + * flow library. + */ + +private import DataFlowImplCommon +private import DataFlowImplSpecific::Private +import DataFlowImplSpecific::Public +private import DataFlowImpl +import DataFlowImplCommonPublic +import FlowStateString + +/** + * A configuration of interprocedural data flow analysis. This defines + * sources, sinks, and any other configurable aspect of the analysis. Each + * use of the global data flow library must define its own unique extension + * of this abstract class. To create a configuration, extend this class with + * a subclass whose characteristic predicate is a unique singleton string. + * For example, write + * + * ```ql + * class MyAnalysisConfiguration extends DataFlow::Configuration { + * MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" } + * // Override `isSource` and `isSink`. + * // Optionally override `isBarrier`. + * // Optionally override `isAdditionalFlowStep`. + * } + * ``` + * Conceptually, this defines a graph where the nodes are `DataFlow::Node`s and + * the edges are those data-flow steps that preserve the value of the node + * along with any additional edges defined by `isAdditionalFlowStep`. + * Specifying nodes in `isBarrier` will remove those nodes from the graph, and + * specifying nodes in `isBarrierIn` and/or `isBarrierOut` will remove in-going + * and/or out-going edges from those nodes, respectively. + * + * Then, to query whether there is flow between some `source` and `sink`, + * write + * + * ```ql + * exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink)) + * ``` + * + * Multiple configurations can coexist, but two classes extending + * `DataFlow::Configuration` should never depend on each other. One of them + * should instead depend on a `DataFlow2::Configuration`, a + * `DataFlow3::Configuration`, or a `DataFlow4::Configuration`. + */ +abstract class Configuration extends string { + bindingset[this] + Configuration() { any() } + + /** + * Holds if `source` is a relevant data flow source. + */ + predicate isSource(Node source) { none() } + + /** + * Holds if `source` is a relevant data flow source with the given initial + * `state`. + */ + predicate isSource(Node source, FlowState state) { none() } + + /** + * Holds if `sink` is a relevant data flow sink. + */ + predicate isSink(Node sink) { none() } + + /** + * Holds if `sink` is a relevant data flow sink accepting `state`. + */ + predicate isSink(Node sink, FlowState state) { none() } + + /** + * Holds if data flow through `node` is prohibited. This completely removes + * `node` from the data flow graph. + */ + predicate isBarrier(Node node) { none() } + + /** + * Holds if data flow through `node` is prohibited when the flow state is + * `state`. + */ + predicate isBarrier(Node node, FlowState state) { none() } + + /** Holds if data flow into `node` is prohibited. */ + predicate isBarrierIn(Node node) { none() } + + /** Holds if data flow out of `node` is prohibited. */ + predicate isBarrierOut(Node node) { none() } + + /** + * DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead. + * + * Holds if data flow through nodes guarded by `guard` is prohibited. + */ + deprecated predicate isBarrierGuard(BarrierGuard guard) { none() } + + /** + * DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead. + * + * Holds if data flow through nodes guarded by `guard` is prohibited when + * the flow state is `state` + */ + deprecated predicate isBarrierGuard(BarrierGuard guard, FlowState state) { none() } + + /** + * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. + */ + predicate isAdditionalFlowStep(Node node1, Node node2) { none() } + + /** + * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. + * This step is only applicable in `state1` and updates the flow state to `state2`. + */ + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + none() + } + + /** + * Holds if an arbitrary number of implicit read steps of content `c` may be + * taken at `node`. + */ + predicate allowImplicitRead(Node node, ContentSet c) { none() } + + /** + * Gets the virtual dispatch branching limit when calculating field flow. + * This can be overridden to a smaller value to improve performance (a + * value of 0 disables field flow), or a larger value to get more results. + */ + int fieldFlowBranchLimit() { result = 2 } + + /** + * Gets a data flow configuration feature to add restrictions to the set of + * valid flow paths. + * + * - `FeatureHasSourceCallContext`: + * Assume that sources have some existing call context to disallow + * conflicting return-flow directly following the source. + * - `FeatureHasSinkCallContext`: + * Assume that sinks have some existing call context to disallow + * conflicting argument-to-parameter flow directly preceding the sink. + * - `FeatureEqualSourceSinkCallContext`: + * Implies both of the above and additionally ensures that the entire flow + * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. + */ + FlowFeature getAFeature() { none() } + + /** Holds if sources should be grouped in the result of `hasFlowPath`. */ + predicate sourceGrouping(Node source, string sourceGroup) { none() } + + /** Holds if sinks should be grouped in the result of `hasFlowPath`. */ + predicate sinkGrouping(Node sink, string sinkGroup) { none() } + + /** + * Holds if data may flow from `source` to `sink` for this configuration. + */ + predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) } + + /** + * Holds if data may flow from `source` to `sink` for this configuration. + * + * The corresponding paths are generated from the end-points and the graph + * included in the module `PathGraph`. + */ + predicate hasFlowPath(PathNode source, PathNode sink) { hasFlowPath(source, sink, this) } + + /** + * Holds if data may flow from some source to `sink` for this configuration. + */ + predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) } + + /** + * Holds if data may flow from some source to `sink` for this configuration. + */ + predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } + + /** + * DEPRECATED: Use `FlowExploration` instead. + * + * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` + * measured in approximate number of interprocedural steps. + */ + deprecated int explorationLimit() { none() } + + /** + * Holds if hidden nodes should be included in the data flow graph. + * + * This feature should only be used for debugging or when the data flow graph + * is not visualized (for example in a `path-problem` query). + */ + predicate includeHiddenNodes() { none() } +} + +/** + * This class exists to prevent mutual recursion between the user-overridden + * member predicates of `Configuration` and the rest of the data-flow library. + * Good performance cannot be guaranteed in the presence of such recursion, so + * it should be replaced by using more than one copy of the data flow library. + */ +abstract private class ConfigurationRecursionPrevention extends Configuration { + bindingset[this] + ConfigurationRecursionPrevention() { any() } + + override predicate hasFlow(Node source, Node sink) { + strictcount(Node n | this.isSource(n)) < 0 + or + strictcount(Node n | this.isSource(n, _)) < 0 + or + strictcount(Node n | this.isSink(n)) < 0 + or + strictcount(Node n | this.isSink(n, _)) < 0 + or + strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0 + or + strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, _, n2, _)) < 0 + or + super.hasFlow(source, sink) + } +} + +/** A bridge class to access the deprecated `isBarrierGuard`. */ +private class BarrierGuardGuardedNodeBridge extends Unit { + abstract predicate guardedNode(Node n, Configuration config); + + abstract predicate guardedNode(Node n, FlowState state, Configuration config); +} + +private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { + deprecated override predicate guardedNode(Node n, Configuration config) { + exists(BarrierGuard g | + config.isBarrierGuard(g) and + n = g.getAGuardedNode() + ) + } + + deprecated override predicate guardedNode(Node n, FlowState state, Configuration config) { + exists(BarrierGuard g | + config.isBarrierGuard(g, state) and + n = g.getAGuardedNode() + ) + } +} + +private FlowState relevantState(Configuration config) { + config.isSource(_, result) or + config.isSink(_, result) or + config.isBarrier(_, result) or + config.isAdditionalFlowStep(_, result, _, _) or + config.isAdditionalFlowStep(_, _, _, result) +} + +private newtype TConfigState = + TMkConfigState(Configuration config, FlowState state) { + state = relevantState(config) or state instanceof FlowStateEmpty + } + +private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) } + +private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) } + +private predicate singleConfiguration() { 1 = strictcount(Configuration c) } + +private module Config implements FullStateConfigSig { + class FlowState = TConfigState; + + predicate isSource(Node source, FlowState state) { + getConfig(state).isSource(source, getState(state)) + or + getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty + } + + predicate isSink(Node sink, FlowState state) { + getConfig(state).isSink(sink, getState(state)) + or + getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty + } + + predicate isBarrier(Node node) { none() } + + predicate isBarrier(Node node, FlowState state) { + getConfig(state).isBarrier(node, getState(state)) or + getConfig(state).isBarrier(node) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state)) + } + + predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) } + + predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) } + + predicate isAdditionalFlowStep(Node node1, Node node2) { + singleConfiguration() and + any(Configuration config).isAdditionalFlowStep(node1, node2) + } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and + getConfig(state2) = getConfig(state1) + or + not singleConfiguration() and + getConfig(state1).isAdditionalFlowStep(node1, node2) and + state2 = state1 + } + + predicate allowImplicitRead(Node node, ContentSet c) { + any(Configuration config).allowImplicitRead(node, c) + } + + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } + + FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } + + predicate sourceGrouping(Node source, string sourceGroup) { + any(Configuration config).sourceGrouping(source, sourceGroup) + } + + predicate sinkGrouping(Node sink, string sinkGroup) { + any(Configuration config).sinkGrouping(sink, sinkGroup) + } + + predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() } +} + +private import Impl as I +import I + +/** + * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. + * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. + */ +class PathNode instanceof I::PathNode { + /** Gets a textual representation of this element. */ + final string toString() { result = super.toString() } + + /** + * Gets a textual representation of this element, including a textual + * representation of the call context. + */ + final string toStringWithContext() { result = super.toStringWithContext() } + + /** + * 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/). + */ + final predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + + /** Gets the underlying `Node`. */ + final Node getNode() { result = super.getNode() } + + /** Gets the `FlowState` of this node. */ + final FlowState getState() { result = getState(super.getState()) } + + /** Gets the associated configuration. */ + final Configuration getConfiguration() { result = getConfig(super.getState()) } + + /** Gets a successor of this node, if any. */ + final PathNode getASuccessor() { result = super.getASuccessor() } + + /** Holds if this node is a source. */ + final predicate isSource() { super.isSource() } + + /** Holds if this node is a grouping of source nodes. */ + final predicate isSourceGroup(string group) { super.isSourceGroup(group) } + + /** Holds if this node is a grouping of sink nodes. */ + final predicate isSinkGroup(string group) { super.isSinkGroup(group) } +} + +private predicate hasFlow(Node source, Node sink, Configuration config) { + exists(PathNode source0, PathNode sink0 | + hasFlowPath(source0, sink0, config) and + source0.getNode() = source and + sink0.getNode() = sink + ) +} + +private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) { + hasFlowPath(source, sink) and source.getConfiguration() = config +} + +private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) } + +predicate flowsTo = hasFlow/3; diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll index 1b969756b09..e6bdc74cceb 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll @@ -1,16 +1,16 @@ /** - * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from - * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed - * through the `Configuration` class. This file exists in several identical - * copies, allowing queries to use multiple `Configuration` classes that depend - * on each other without introducing mutual recursion among those configurations. + * DEPRECATED: Use `Make` and `MakeWithState` instead. + * + * Provides a `Configuration` class backwards-compatible interface to the data + * flow library. */ private import DataFlowImplCommon private import DataFlowImplSpecific::Private import DataFlowImplSpecific::Public +private import DataFlowImpl import DataFlowImplCommonPublic +import FlowStateString /** * A configuration of interprocedural data flow analysis. This defines @@ -144,6 +144,10 @@ abstract class Configuration extends string { * - `FeatureEqualSourceSinkCallContext`: * Implies both of the above and additionally ensures that the entire flow * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. */ FlowFeature getAFeature() { none() } @@ -156,7 +160,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from `source` to `sink` for this configuration. */ - predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } + predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) } /** * Holds if data may flow from `source` to `sink` for this configuration. @@ -169,9 +173,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from some source to `sink` for this configuration. */ - predicate hasFlowTo(Node sink) { - sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode() - } + predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) } /** * Holds if data may flow from some source to `sink` for this configuration. @@ -179,10 +181,12 @@ abstract class Configuration extends string { predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } /** + * DEPRECATED: Use `FlowExploration` instead. + * * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` * measured in approximate number of interprocedural steps. */ - int explorationLimit() { none() } + deprecated int explorationLimit() { none() } /** * Holds if hidden nodes should be included in the data flow graph. @@ -191,49 +195,6 @@ abstract class Configuration extends string { * is not visualized (for example in a `path-problem` query). */ predicate includeHiddenNodes() { none() } - - /** - * Holds if there is a partial data flow path from `source` to `node`. The - * approximate distance between `node` and the closest source is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards sink definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sources is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - */ - final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { - partialFlow(source, node, this) and - dist = node.getSourceDistance() - } - - /** - * Holds if there is a partial data flow path from `node` to `sink`. The - * approximate distance between `node` and the closest sink is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards source definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sinks is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - * - * Note that reverse flow has slightly lower precision than the corresponding - * forward flow, as reverse flow disregards type pruning among other features. - */ - final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { - revPartialFlow(node, sink, this) and - dist = node.getSinkDistance() - } } /** @@ -263,90 +224,6 @@ abstract private class ConfigurationRecursionPrevention extends Configuration { } } -private newtype TNodeEx = - TNodeNormal(Node n) or - TNodeImplicitRead(Node n, boolean hasRead) { - any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true] - } - -private class NodeEx extends TNodeEx { - string toString() { - result = this.asNode().toString() - or - exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") - } - - Node asNode() { this = TNodeNormal(result) } - - predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } - - Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } - - pragma[nomagic] - private DataFlowCallable getEnclosingCallable0() { - nodeEnclosingCallable(this.projectToNode(), result) - } - - pragma[inline] - DataFlowCallable getEnclosingCallable() { - pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) - } - - pragma[nomagic] - private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } - - pragma[inline] - DataFlowType getDataFlowType() { - pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) - } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -private class ArgNodeEx extends NodeEx { - ArgNodeEx() { this.asNode() instanceof ArgNode } -} - -private class ParamNodeEx extends NodeEx { - ParamNodeEx() { this.asNode() instanceof ParamNode } - - predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { - this.asNode().(ParamNode).isParameterOf(c, pos) - } - - ParameterPosition getPosition() { this.isParameterOf(_, result) } -} - -private class RetNodeEx extends NodeEx { - RetNodeEx() { this.asNode() instanceof ReturnNodeExt } - - ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } - - ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } -} - -private predicate inBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierIn(n) - | - config.isSource(n) or config.isSource(n, _) - ) -} - -private predicate outBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierOut(n) - | - config.isSink(n) or config.isSink(n, _) - ) -} - /** A bridge class to access the deprecated `isBarrierGuard`. */ private class BarrierGuardGuardedNodeBridge extends Unit { abstract predicate guardedNode(Node n, Configuration config); @@ -370,3019 +247,94 @@ private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { } } -pragma[nomagic] -private predicate fullBarrier(NodeEx node, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n) +private FlowState relevantState(Configuration config) { + config.isSource(_, result) or + config.isSink(_, result) or + config.isBarrier(_, result) or + config.isAdditionalFlowStep(_, result, _, _) or + config.isAdditionalFlowStep(_, _, _, result) +} + +private newtype TConfigState = + TMkConfigState(Configuration config, FlowState state) { + state = relevantState(config) or state instanceof FlowStateEmpty + } + +private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) } + +private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) } + +private predicate singleConfiguration() { 1 = strictcount(Configuration c) } + +private module Config implements FullStateConfigSig { + class FlowState = TConfigState; + + predicate isSource(Node source, FlowState state) { + getConfig(state).isSource(source, getState(state)) or - config.isBarrierIn(n) and - not config.isSource(n) and - not config.isSource(n, _) + getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty + } + + predicate isSink(Node sink, FlowState state) { + getConfig(state).isSink(sink, getState(state)) or - config.isBarrierOut(n) and - not config.isSink(n) and - not config.isSink(n, _) + getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty + } + + predicate isBarrier(Node node) { none() } + + predicate isBarrier(Node node, FlowState state) { + getConfig(state).isBarrier(node, getState(state)) or + getConfig(state).isBarrier(node) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state)) + } + + predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) } + + predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) } + + predicate isAdditionalFlowStep(Node node1, Node node2) { + singleConfiguration() and + any(Configuration config).isAdditionalFlowStep(node1, node2) + } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and + getConfig(state2) = getConfig(state1) or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config) - ) -} - -pragma[nomagic] -private predicate stateBarrier(NodeEx node, FlowState state, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n, state) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config) - ) -} - -pragma[nomagic] -private predicate sourceNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSource(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSource(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -pragma[nomagic] -private predicate sinkNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSink(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSink(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -/** Provides the relevant barriers for a step from `node1` to `node2`. */ -pragma[inline] -private predicate stepFilter(NodeEx node1, NodeEx node2, Configuration config) { - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) -} - -/** - * Holds if data can flow in one local step from `node1` to `node2`. - */ -private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.asNode() = n and - node2.isImplicitReadNode(n, false) and - not fullBarrier(node1, config) - ) -} - -/** - * Holds if the additional step from `node1` to `node2` does not jump between callables. - */ -private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.isImplicitReadNode(n, true) and - node2.asNode() = n and - not fullBarrier(node2, config) - ) -} - -private predicate additionalLocalStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) - ) -} - -/** - * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. - */ -private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if the additional step from `node1` to `node2` jumps between callables. - */ -private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate additionalJumpStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -pragma[nomagic] -private predicate readSet(NodeEx node1, ContentSet c, NodeEx node2, Configuration config) { - readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and - stepFilter(node1, node2, config) - or - exists(Node n | - node2.isImplicitReadNode(n, true) and - node1.isImplicitReadNode(n, _) and - config.allowImplicitRead(n, c) - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(ContentSet cs | - readSet(node1, cs, node2, config) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate clearsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - clearsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate expectsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - expectsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -pragma[nomagic] -private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } - -pragma[nomagic] -private predicate hasReadStep(Content c, Configuration config) { read(_, c, _, config) } - -pragma[nomagic] -private predicate store( - NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config -) { - store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), - contentType) and - hasReadStep(tc.getContent(), config) and - stepFilter(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { - viableReturnPosOut(call, pos, out.asNode()) -} - -pragma[nomagic] -private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { - viableParamArg(call, p.asNode(), arg.asNode()) -} - -/** - * Holds if field flow should be used for the given configuration. - */ -private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } - -private predicate hasSourceCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSourceCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate hasSinkCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSinkCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if flow from `p` to a return node of kind `kind` is allowed. - * - * We don't expect a parameter to return stored in itself, unless - * explicitly allowed - */ -bindingset[p, kind] -private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { - exists(ParameterPosition pos | p.isParameterOf(_, pos) | - not kind.(ParamUpdateReturnKind).getPosition() = pos - or - allowParameterReturnInSelfCached(p.asNode()) - ) -} - -private module Stage1 implements StageSig { - class Ap extends int { - // workaround for bad functionality-induced joins (happens when using `Unit`) - pragma[nomagic] - Ap() { this in [0 .. 1] and this < 1 } - } - - private class Cc = boolean; - - /* Begin: Stage 1 logic. */ - /** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `cc` records whether the node is reached through an - * argument in a call. - */ - private predicate fwdFlow(NodeEx node, Cc cc, Configuration config) { - sourceNode(node, _, config) and - if hasSourceCallCtx(config) then cc = true else cc = false - or - exists(NodeEx mid | fwdFlow(mid, cc, config) | - localFlowStep(mid, node, config) or - additionalLocalFlowStep(mid, node, config) or - additionalLocalStateStep(mid, _, node, _, config) - ) - or - exists(NodeEx mid | fwdFlow(mid, _, config) and cc = false | - jumpStep(mid, node, config) or - additionalJumpStep(mid, node, config) or - additionalJumpStateStep(mid, _, node, _, config) - ) - or - // store - exists(NodeEx mid | - useFieldFlow(config) and - fwdFlow(mid, cc, config) and - store(mid, _, node, _, config) - ) - or - // read - exists(ContentSet c | - fwdFlowReadSet(c, node, cc, config) and - fwdFlowConsCandSet(c, _, config) - ) - or - // flow into a callable - fwdFlowIn(_, _, _, node, config) and - cc = true - or - // flow out of a callable - fwdFlowOut(_, node, false, config) and - cc = false - or - // flow through a callable - exists(DataFlowCall call | - fwdFlowOutFromArg(call, node, config) and - fwdFlowIsEntered(call, cc, config) - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowIn( - DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p, Configuration config - ) { - // call context cannot help reduce virtual dispatch - fwdFlow(arg, cc, config) and - viableParamArgEx(call, p, arg) and - not fullBarrier(p, config) and - ( - cc = false - or - cc = true and - not reducedViableImplInCallContext(call, _, _) - ) - or - // call context may help reduce virtual dispatch - exists(DataFlowCallable target | - fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target, config) and - target = viableImplInSomeFwdFlowCallContextExt(call, config) and - cc = true - ) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - fwdFlowIn(call, _, cc, _, config) - } - - pragma[nomagic] - private predicate fwdFlowInReducedViableImplInSomeCallContext( - DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target, Configuration config - ) { - fwdFlow(arg, true, config) and - viableParamArgEx(call, p, arg) and - reducedViableImplInCallContext(call, _, _) and - target = p.getEnclosingCallable() and - not fullBarrier(p, config) - } - - /** - * Gets a viable dispatch target of `call` in the context `ctx`. This is - * restricted to those `call`s for which a context might make a difference, - * and to `ctx`s that are reachable in `fwdFlow`. - */ - pragma[nomagic] - private DataFlowCallable viableImplInSomeFwdFlowCallContextExt( - DataFlowCall call, Configuration config - ) { - exists(DataFlowCall ctx | - fwdFlowIsEntered(ctx, _, config) and - result = viableImplInCallContextExt(call, ctx) - ) - } - - private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) } - - pragma[nomagic] - private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc, Configuration config) { - exists(NodeEx mid | - fwdFlow(mid, cc, config) and - readSet(mid, c, node, config) - ) - } - - /** - * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - fwdFlow(mid, _, config) and - store(mid, tc, node, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `cs` may be interpreted in a read as the target of some store - * into `c`, in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCandSet(ContentSet cs, Content c, Configuration config) { - fwdFlowConsCand(c, config) and - c = cs.getAReadContent() - } - - pragma[nomagic] - private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { - exists(RetNodeEx ret | - fwdFlow(ret, cc, config) and - ret.getReturnPosition() = pos - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) { - exists(ReturnPosition pos | - fwdFlowReturnPosition(pos, cc, config) and - viableReturnPosOutEx(call, pos, out) and - not fullBarrier(out, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) { - fwdFlowOut(call, out, true, config) - } - - private predicate stateStepFwd(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1 | - additionalLocalStateStep(node1, state1, _, state2, config) or - additionalJumpStateStep(node1, state1, _, state2, config) - | - fwdFlow(node1, config) - ) - } - - private predicate fwdFlowState(FlowState state, Configuration config) { - sourceNode(_, state, config) - or - exists(FlowState state0 | - fwdFlowState(state0, config) and - stateStepFwd(state0, state, config) - ) - } - - /** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink. - */ - pragma[nomagic] - private predicate revFlow(NodeEx node, boolean toReturn, Configuration config) { - revFlow0(node, toReturn, config) and - fwdFlow(node, config) - } - - pragma[nomagic] - private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) { - exists(FlowState state | - fwdFlow(node, pragma[only_bind_into](config)) and - sinkNode(node, state, config) and - fwdFlowState(state, pragma[only_bind_into](config)) and - if hasSinkCallCtx(config) then toReturn = true else toReturn = false - ) - or - exists(NodeEx mid | revFlow(mid, toReturn, config) | - localFlowStep(node, mid, config) or - additionalLocalFlowStep(node, mid, config) or - additionalLocalStateStep(node, _, mid, _, config) - ) - or - exists(NodeEx mid | revFlow(mid, _, config) and toReturn = false | - jumpStep(node, mid, config) or - additionalJumpStep(node, mid, config) or - additionalJumpStateStep(node, _, mid, _, config) - ) - or - // store - exists(Content c | - revFlowStore(c, node, toReturn, config) and - revFlowConsCand(c, config) - ) - or - // read - exists(NodeEx mid, ContentSet c | - readSet(node, c, mid, config) and - fwdFlowConsCandSet(c, _, pragma[only_bind_into](config)) and - revFlow(mid, toReturn, pragma[only_bind_into](config)) - ) - or - // flow into a callable - revFlowIn(_, node, false, config) and - toReturn = false - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(pos, config) and - node.(RetNodeEx).getReturnPosition() = pos and - toReturn = true - ) - or - // flow through a callable - exists(DataFlowCall call | - revFlowInToReturn(call, node, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - /** - * Holds if `c` is the target of a read in the flow covered by `revFlow`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, ContentSet cs | - fwdFlow(node, pragma[only_bind_into](config)) and - readSet(node, cs, mid, config) and - fwdFlowConsCandSet(cs, c, pragma[only_bind_into](config)) and - revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) { - exists(NodeEx mid, TypedContent tc | - revFlow(mid, toReturn, pragma[only_bind_into](config)) and - fwdFlowConsCand(c, pragma[only_bind_into](config)) and - store(node, tc, mid, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `c` is the target of both a read and a store in the flow covered - * by `revFlow`. - */ - pragma[nomagic] - additional predicate revFlowIsReadAndStored(Content c, Configuration conf) { - revFlowConsCand(c, conf) and - revFlowStore(c, _, _, conf) - } - - pragma[nomagic] - additional predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config - ) { - fwdFlowReturnPosition(pos, _, config) and - viableReturnPosOutEx(call, pos, out) - } - - pragma[nomagic] - private predicate revFlowOut(ReturnPosition pos, Configuration config) { - exists(NodeEx out | - revFlow(out, _, config) and - viableReturnPosOutNodeCandFwd1(_, pos, out, config) - ) - } - - pragma[nomagic] - additional predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config - ) { - fwdFlowIn(call, arg, _, p, config) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate revFlowIn( - DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config - ) { - exists(ParamNodeEx p | - revFlow(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) { - revFlowIn(call, arg, true, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(NodeEx out | - revFlow(out, toReturn, config) and - fwdFlowOutFromArg(call, out, config) - ) - } - - private predicate stateStepRev(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - additionalLocalStateStep(node1, state1, node2, state2, config) or - additionalJumpStateStep(node1, state1, node2, state2, config) - | - revFlow(node1, _, pragma[only_bind_into](config)) and - revFlow(node2, _, pragma[only_bind_into](config)) and - fwdFlowState(state1, pragma[only_bind_into](config)) and - fwdFlowState(state2, pragma[only_bind_into](config)) - ) - } - - additional predicate revFlowState(FlowState state, Configuration config) { - exists(NodeEx node | - sinkNode(node, state, config) and - revFlow(node, _, pragma[only_bind_into](config)) and - fwdFlowState(state, pragma[only_bind_into](config)) - ) - or - exists(FlowState state0 | - revFlowState(state0, config) and - stateStepRev(state, state0, config) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Content c | - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - revFlow(node2, pragma[only_bind_into](config)) and - store(node1, tc, node2, contentType, config) and - c = tc.getContent() and - exists(ap1) - ) - } - - pragma[nomagic] - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) { - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - read(n1, c, n2, pragma[only_bind_into](config)) and - revFlow(n2, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, config) and - exists(ap) - } - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, _, pragma[only_bind_into](config)) and - exists(state) and - exists(ap) - } - - private predicate throughFlowNodeCand(NodeEx node, Configuration config) { - revFlow(node, true, config) and - fwdFlow(node, true, config) and - not inBarrier(node, config) and - not outBarrier(node, config) - } - - /** Holds if flow may return from `callable`. */ - pragma[nomagic] - private predicate returnFlowCallableNodeCand( - DataFlowCallable callable, ReturnKindExt kind, Configuration config - ) { - exists(RetNodeEx ret | - throughFlowNodeCand(ret, config) and - callable = ret.getEnclosingCallable() and - kind = ret.getKind() - ) - } - - /** - * Holds if flow may enter through `p` and reach a return node making `p` a - * candidate for the origin of a summary. - */ - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(DataFlowCallable c, ReturnKindExt kind | - throughFlowNodeCand(p, config) and - returnFlowCallableNodeCand(c, kind, config) and - p.getEnclosingCallable() = c and - exists(ap) and - parameterFlowThroughAllowed(p, kind) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - throughFlowNodeCand(ret, config) and - kind = ret.getKind() and - exists(argAp) and - exists(ap) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, boolean toReturn | - revFlow(arg, toReturn, config) and - revFlowInToReturn(call, arg, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, config)) and - fields = count(Content f0 | fwdFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | fwdFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, config)) and - fields = count(Content f0 | revFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | revFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | revFlow(n, b, config)) - } - /* End: Stage 1 logic. */ -} - -pragma[noinline] -private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - localFlowStep(node1, node2, config) -} - -pragma[noinline] -private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - additionalLocalFlowStep(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCand1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config -) { - Stage1::revFlow(out, config) and - Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, Configuration config -) { - exists(ReturnPosition pos | - viableReturnPosOutNodeCand1(call, pos, out, config) and - pos = ret.getReturnPosition() and - kind = pos.getKind() and - Stage1::revFlow(ret, config) and - not outBarrier(ret, config) and - not inBarrier(out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCand1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config -) { - Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and - Stage1::revFlow(arg, config) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config -) { - viableParamArgNodeCand1(call, p, arg, config) and - Stage1::revFlow(p, config) and - not outBarrier(arg, config) and - not inBarrier(p, config) -} - -/** - * Gets the amount of forward branching on the origin of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int branch(NodeEx n1, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n1, _, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) - ) -} - -/** - * Gets the amount of backward branching on the target of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int join(NodeEx n2, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n, _, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) - ) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. The - * `allowsFieldFlow` flag indicates whether the branching is within the limit - * specified by the configuration. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, ret, kind, out, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(ret, pragma[only_bind_into](config)) and - j = join(out, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. The `allowsFieldFlow` flag indicates whether - * the branching is within the limit specified by the configuration. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config -) { - flowIntoCallNodeCand1(call, arg, p, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(arg, pragma[only_bind_into](config)) and - j = join(p, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -private signature module StageSig { - class Ap; - - predicate revFlow(NodeEx node, Configuration config); - - predicate revFlowAp(NodeEx node, Ap ap, Configuration config); - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config); - - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config); - - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config); - - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ); - - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ); - - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config); -} - -private module MkStage { - class ApApprox = PrevStage::Ap; - - signature module StageParam { - class Ap; - - class ApNil extends Ap; - - bindingset[result, ap] - ApApprox getApprox(Ap ap); - - ApNil getApNil(NodeEx node); - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail); - - /** - * An approximation of `Content` that corresponds to the precision level of - * `Ap`, such that the mappings from both `Ap` and `Content` to this type - * are functional. - */ - class ApHeadContent; - - ApHeadContent getHeadContent(Ap ap); - - ApHeadContent projectToHeadContent(Content c); - - class ApOption; - - ApOption apNone(); - - ApOption apSome(Ap ap); - - class Cc; - - class CcCall extends Cc; - - // TODO: member predicate on CcCall - predicate matchesCall(CcCall cc, DataFlowCall call); - - class CcNoCall extends Cc; - - Cc ccNone(); - - CcCall ccSomeCall(); - - class LocalCc; - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc); - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ); - - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config - ); - - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config - ); - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config); - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType); - } - - module Stage implements StageSig { - import Param - - /* Begin: Stage logic. */ - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - private predicate revFlowApAlias(NodeEx node, ApApprox apa, Configuration config) { - PrevStage::revFlowAp(node, apa, config) - } - - pragma[nomagic] - private predicate flowIntoCallApa( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa, - Configuration config - ) { - flowIntoCall(call, arg, p, allowsFieldFlow, config) and - PrevStage::revFlowAp(p, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(arg, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowOutOfCallApa( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - ApApprox apa, Configuration config - ) { - flowOutOfCall(call, ret, kind, out, allowsFieldFlow, config) and - PrevStage::revFlowAp(out, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(ret, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowThroughOutOfCall( - DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, - ApApprox argApa, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa, pragma[only_bind_into](config)) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) and - matchesCall(ccc, call) - ) - } - - /** - * Holds if `node` is reachable with access path `ap` from a source in the - * configuration `config`. - * - * The call context `cc` records whether the node is reached through an - * argument in a call, and if so, `summaryCtx` and `argAp` record the - * corresponding parameter position and access path of that argument, respectively. - */ - pragma[nomagic] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa, config) and - PrevStage::revFlow(node, state, apa, config) and - filter(node, state, ap, config) - } - - pragma[inline] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, cc, summaryCtx, argAp, ap, _, config) - } - - pragma[nomagic] - private predicate fwdFlow0( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - sourceNode(node, state, config) and - (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and - argAp = apNone() and - summaryCtx = TParamNodeNone() and - ap = getApNil(node) and - apa = getApprox(ap) - or - exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | - fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0, config) and - localCc = getLocalCc(mid, cc) - | - localStep(mid, state0, node, state, true, _, config, localCc) and - ap = ap0 and - apa = apa0 - or - localStep(mid, state0, node, state, false, ap, config, localCc) and - ap0 instanceof ApNil and - apa = getApprox(ap) - ) - or - exists(NodeEx mid | - fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa, pragma[only_bind_into](config)) and - jumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(mid, state, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(mid, state0, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStateStep(mid, state0, node, state, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - // store - exists(TypedContent tc, Ap ap0 | - fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp, config) and - ap = apCons(tc, ap0) and - apa = getApprox(ap) - ) - or - // read - exists(Ap ap0, Content c | - fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp, config) and - fwdFlowConsCand(ap0, c, ap, config) and - apa = getApprox(ap) - ) - or - // flow into a callable - fwdFlowIn(_, node, state, _, cc, _, _, ap, apa, config) and - if PrevStage::parameterMayFlowThrough(node, apa, config) - then ( - summaryCtx = TParamNodeSome(node.asNode()) and - argAp = apSome(ap) - ) else ( - summaryCtx = TParamNodeNone() and argAp = apNone() - ) - or - // flow out of a callable - exists( - DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, - DataFlowCallable inner - | - fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa, config) and - flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa, config) and - inner = ret.getEnclosingCallable() and - cc = getCallContextReturn(inner, call, innercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - or - // flow through a callable - exists( - DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, ApApprox innerArgApa - | - fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowStore( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(DataFlowType contentType, ApApprox apa1 | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1, config) and - PrevStage::storeStepCand(node1, apa1, tc, node2, contentType, config) and - typecheckStore(ap1, contentType) - ) - } - - /** - * Holds if forward flow with access path `tail` reaches a store of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(TypedContent tc | - fwdFlowStore(_, tail, tc, _, _, _, _, _, config) and - tc.getContent() = c and - cons = apCons(tc, tail) - ) - } - - pragma[nomagic] - private predicate readStepCand( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - PrevStage::readStepCand(node1, c, node2, config) and - apc = projectToHeadContent(c) - } - - bindingset[node1, apc] - pragma[inline_late] - private predicate readStepCand0( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - readStepCand(node1, apc, c, node2, config) - } - - pragma[nomagic] - private predicate fwdFlowRead( - Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(ApHeadContent apc | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap, config) and - apc = getHeadContent(ap) and - readStepCand0(node1, apc, c, node2, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowIn( - DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, - ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa, Configuration config - ) { - exists(ArgNodeEx arg, boolean allowsFieldFlow | - fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa, config) and - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowRetFromArg( - RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, ApApprox argApa, - Ap ap, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - fwdFlow(pragma[only_bind_into](ret), state, ccc, - TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), - pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa), - pragma[only_bind_into](config)) and - kind = ret.getKind() and - parameterFlowThroughAllowed(summaryCtx, kind) and - argApa = getApprox(argAp) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) - ) - } - - pragma[inline] - private predicate fwdFlowThrough0( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, - Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa, config) and - fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp, config) - } - - pragma[nomagic] - private predicate fwdFlowThrough( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa, - config) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` - * and data might flow through the target callable and back out at `call`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered( - DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, - ParamNodeEx p, Ap ap, Configuration config - ) { - exists(ApApprox apa | - fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(p, apa, config) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate storeStepFwd( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config - ) { - fwdFlowStore(node1, ap1, tc, node2, _, _, _, _, config) and - ap2 = apCons(tc, ap1) and - fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _, config) - } - - private predicate readStepFwd( - NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config - ) { - fwdFlowRead(ap1, c, n1, n2, _, _, _, _, config) and - fwdFlowConsCand(ap1, c, ap2, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough0( - DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, - ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, - innerArgApa, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough( - RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, - Ap ap, Configuration config - ) { - exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | - returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa, config) and - pos = ret.getReturnPosition() and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowThroughIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap, - Configuration config - ) { - exists(ApApprox argApa | - flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), - allowsFieldFlow, argApa, pragma[only_bind_into](config)) and - fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa, - pragma[only_bind_into](config)) and - returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap, - pragma[only_bind_into](config)) and - if allowsFieldFlow = false then argAp instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowIntoCallAp( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap, - Configuration config - ) { - exists(ApApprox apa | - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - fwdFlow(arg, _, _, _, _, ap, apa, config) - ) - } - - pragma[nomagic] - private predicate flowOutOfCallAp( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, - Ap ap, Configuration config - ) { - exists(ApApprox apa | - flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa, config) and - fwdFlow(ret, _, _, _, _, ap, apa, config) and - pos = ret.getReturnPosition() - ) - } - - /** - * Holds if `node` with access path `ap` is part of a path from a source to a - * sink in the configuration `config`. - * - * The parameter `returnCtx` records whether (and how) the node must be returned - * from the enclosing callable in order to reach a sink, and if so, `returnAp` - * records the access path of the returned value. - */ - pragma[nomagic] - additional predicate revFlow( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - revFlow0(node, state, returnCtx, returnAp, ap, config) and - fwdFlow(node, state, _, _, _, ap, config) - } - - pragma[nomagic] - private predicate revFlow0( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, _, _, _, ap, config) and - sinkNode(node, state, config) and - ( - if hasSinkCallCtx(config) - then returnCtx = TReturnCtxNoFlowThrough() - else returnCtx = TReturnCtxNone() - ) and - returnAp = apNone() and - ap instanceof ApNil - or - exists(NodeEx mid, FlowState state0 | - localStep(node, state, mid, state0, true, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, ap, config) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap, pragma[only_bind_into](config)) and - localStep(node, pragma[only_bind_into](state), mid, state0, false, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, nil, pragma[only_bind_into](config)) and - ap instanceof ApNil - ) - or - exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, state, _, _, ap, config) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStep(node, mid, config) and - revFlow(pragma[only_bind_into](mid), state, _, _, nil, pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStateStep(node, state, mid, state0, config) and - revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil, - pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - // store - exists(Ap ap0, Content c | - revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp, config) and - revFlowConsCand(ap0, c, ap, config) - ) - or - // read - exists(NodeEx mid, Ap ap0 | - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - readStepFwd(node, ap, _, mid, ap0, config) - ) - or - // flow into a callable - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, state, TReturnCtxNone(), returnAp, ap, config) and - flowIntoCallAp(_, node, p, allowsFieldFlow, ap, config) and - (if allowsFieldFlow = false then ap instanceof ApNil else any()) and - returnCtx = TReturnCtxNone() - ) - or - // flow through a callable - exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, node, p, _, ap, innerReturnAp, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(_, node, pos, state, _, _, ap, config) and - if returnFlowsThrough(node, pos, state, _, _, _, ap, config) - then ( - returnCtx = TReturnCtxMaybeFlowThrough(pos) and - returnAp = apSome(ap) - ) else ( - returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() - ) - ) - } - - pragma[nomagic] - private predicate revFlowStore( - Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, - ReturnCtx returnCtx, ApOption returnAp, Configuration config - ) { - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - storeStepFwd(node, ap, tc, mid, ap0, config) and - tc.getContent() = c - } - - /** - * Holds if reverse flow with access path `tail` reaches a read of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(NodeEx mid, Ap tail0 | - revFlow(mid, _, _, _, tail, config) and - tail = pragma[only_bind_into](tail0) and - readStepFwd(_, cons, c, mid, tail0, config) - ) - } - - pragma[nomagic] - private predicate revFlowOut( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, - ApOption returnAp, Ap ap, Configuration config - ) { - exists(NodeEx out, boolean allowsFieldFlow | - revFlow(out, state, returnCtx, returnAp, ap, config) and - flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate revFlowParamToReturn( - ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap, Configuration config - ) { - revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), - pragma[only_bind_into](ap), pragma[only_bind_into](config)) and - parameterFlowThroughAllowed(p, pos.getKind()) and - PrevStage::parameterMayFlowThrough(p, getApprox(ap), config) - } - - pragma[nomagic] - private predicate revFlowThrough( - DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, - ApOption returnAp, Ap ap, Ap innerReturnAp, Configuration config - ) { - revFlowParamToReturn(p, state, pos, innerReturnAp, ap, config) and - revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned( - DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap, - Configuration config - ) { - exists(RetNodeEx ret, FlowState state, CcCall ccc | - revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap, config) and - returnFlowsThrough(ret, pos, state, ccc, _, _, ap, config) and - matchesCall(ccc, call) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Ap ap2, Content c | - PrevStage::storeStepCand(node1, _, tc, node2, contentType, config) and - revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _, config) and - revFlowConsCand(ap2, c, ap1, config) - ) - } - - predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(Ap ap1, Ap ap2 | - revFlow(node2, _, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and - readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _, - pragma[only_bind_into](config)) - ) - } - - additional predicate revFlow(NodeEx node, FlowState state, Configuration config) { - revFlow(node, state, _, _, _, config) - } - - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, _, _, ap, config) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, _, _, _, ap, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, Configuration config) { - revFlow(node, _, _, _, _, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, ap, config) - } - - private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepFwd(_, ap, tc, _, _, config) - } - - private predicate revConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepCand(_, ap, tc, _, _, config) - } - - private predicate validAp(Ap ap, Configuration config) { - revFlow(_, _, _, _, ap, config) and ap instanceof ApNil - or - exists(TypedContent head, Ap tail | - consCand(head, tail, config) and - ap = apCons(head, tail) - ) - } - - additional predicate consCand(TypedContent tc, Ap ap, Configuration config) { - revConsCand(tc, ap, config) and - validAp(ap, config) - } - - pragma[nomagic] - private predicate parameterFlowsThroughRev( - ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp, Configuration config - ) { - revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap, config) and - parameterFlowThroughAllowed(p, pos.getKind()) - } - - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(ReturnPosition pos | - returnFlowsThrough(_, pos, _, _, p, ap, _, config) and - parameterFlowsThroughRev(p, ap, pos, _, config) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - exists(ParamNodeEx p, ReturnPosition pos | - returnFlowsThrough(ret, pos, _, _, p, argAp, ap, config) and - parameterFlowsThroughRev(p, argAp, pos, ap, config) and - kind = pos.getKind() - ) - } - - pragma[nomagic] - private predicate revFlowThroughArg( - DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, - Ap ap, Configuration config - ) { - exists(ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp, config) - ) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | - revFlow(arg, state, returnCtx, returnAp, ap, config) and - revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, config)) and - fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and - states = count(FlowState state | fwdFlow(_, state, _, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | - fwdFlow(n, state, cc, summaryCtx, argAp, ap, config) - ) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, _, _, _, config)) and - fields = count(TypedContent f0 | consCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and - states = count(FlowState state | revFlow(_, state, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | - revFlow(n, state, returnCtx, retAp, ap, config) - ) - } - /* End: Stage logic. */ - } -} - -private module BooleanCallContext { - class Cc extends boolean { - Cc() { this in [true, false] } - } - - class CcCall extends Cc { - CcCall() { this = true } - } - - /** Holds if the call context may be `call`. */ - predicate matchesCall(CcCall cc, DataFlowCall call) { any() } - - class CcNoCall extends Cc { - CcNoCall() { this = false } - } - - Cc ccNone() { result = false } - - CcCall ccSomeCall() { result = true } - - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } -} - -private module Level1CallContext { - class Cc = CallContext; - - class CcCall = CallContextCall; - - pragma[inline] - predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } - - class CcNoCall = CallContextNoCall; - - Cc ccNone() { result instanceof CallContextAny } - - CcCall ccSomeCall() { result instanceof CallContextSomeCall } - - module NoLocalCallContext { - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSiteDispatch(call, c) - then result = TSpecificCall(call) - else result = TSomeCall() - } - } - - module LocalCallContext { - class LocalCc = LocalCallContext; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { - result = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - node.getEnclosingCallable()) - } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() - } - } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { - checkCallContextReturn(innercc, c, call) and - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() - } -} - -private module Stage2Param implements MkStage::StageParam { - private module PrevStage = Stage1; - - class Ap extends boolean { - Ap() { this in [true, false] } - } - - class ApNil extends Ap { - ApNil() { this = false } - } - - bindingset[result, ap] - PrevStage::Ap getApprox(Ap ap) { any() } - - ApNil getApNil(NodeEx node) { Stage1::revFlow(node, _) and exists(result) } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } - - class ApHeadContent = Unit; - - pragma[inline] - ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } - - ApHeadContent projectToHeadContent(Content c) { any() } - - class ApOption = BooleanOption; - - ApOption apNone() { result = TBooleanNone() } - - ApOption apSome(Ap ap) { result = TBooleanSome(ap) } - - import Level1CallContext - import NoLocalCallContext - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - ( - preservesValue = true and - localFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalStateStep(node1, state1, node2, state2, config) - ) and - exists(ap) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand1/6; - - predicate flowIntoCall = flowIntoCallNodeCand1/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - expectsContentEx(node, c) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - PrevStage::revFlowState(state, pragma[only_bind_into](config)) and - exists(ap) and - not stateBarrier(node, state, config) and - ( - notExpectsContent(node) - or - ap = true and - expectsContentCand(node, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage2 implements StageSig { - import MkStage::Stage -} - -pragma[nomagic] -private predicate flowOutOfCallNodeCand2( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -pragma[nomagic] -private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -private module LocalFlowBigStep { - /** - * A node where some checking is required, and hence the big-step relation - * is not allowed to step over. - */ - private class FlowCheckNode extends NodeEx { - FlowCheckNode() { - castNode(this.asNode()) or - clearsContentCached(this.asNode(), _) or - expectsContentCached(this.asNode(), _) - } - } - - /** - * Holds if `node` can be the first node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowEntry(NodeEx node, FlowState state, Configuration config) { - Stage2::revFlow(node, state, config) and - ( - sourceNode(node, state, config) - or - jumpStep(_, node, config) - or - additionalJumpStep(_, node, config) - or - additionalJumpStateStep(_, _, node, state, config) - or - node instanceof ParamNodeEx - or - node.asNode() instanceof OutNodeExt - or - Stage2::storeStepCand(_, _, _, node, _, config) - or - Stage2::readStepCand(_, _, node, config) - or - node instanceof FlowCheckNode - or - exists(FlowState s | - additionalLocalStateStep(_, s, node, state, config) and - s != state - ) - ) - } - - /** - * Holds if `node` can be the last node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowExit(NodeEx node, FlowState state, Configuration config) { - exists(NodeEx next | Stage2::revFlow(next, state, config) | - jumpStep(node, next, config) or - additionalJumpStep(node, next, config) or - flowIntoCallNodeCand2(_, node, next, _, config) or - flowOutOfCallNodeCand2(_, node, _, next, _, config) or - Stage2::storeStepCand(node, _, _, next, _, config) or - Stage2::readStepCand(node, _, next, config) - ) - or - exists(NodeEx next, FlowState s | Stage2::revFlow(next, s, config) | - additionalJumpStateStep(node, state, next, s, config) - or - additionalLocalStateStep(node, state, next, s, config) and - s != state - ) - or - Stage2::revFlow(node, state, config) and - node instanceof FlowCheckNode - or - sinkNode(node, state, config) - } - - pragma[noinline] - private predicate additionalLocalFlowStepNodeCand2( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, Configuration config - ) { - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 and - Stage2::revFlow(node1, pragma[only_bind_into](state1), false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false, - pragma[only_bind_into](config)) - or - additionalLocalStateStep(node1, state1, node2, state2, config) and - Stage2::revFlow(node1, state1, false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, state2, false, pragma[only_bind_into](config)) - } - - /** - * Holds if the local path from `node1` to `node2` is a prefix of a maximal - * subsequence of local flow steps in a dataflow path. - * - * This is the transitive closure of `[additional]localFlowStep` beginning - * at `localFlowEntry`. - */ - pragma[nomagic] - private predicate localFlowStepPlus( - NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, - Configuration config, LocalCallContext cc - ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and - ( - localFlowEntry(node1, pragma[only_bind_into](state), pragma[only_bind_into](config)) and - ( - localFlowStepNodeCand1(node1, node2, config) and - preservesValue = true and - t = node1.getDataFlowType() and // irrelevant dummy value - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - or - additionalLocalFlowStepNodeCand2(node1, state, node2, state, config) and - preservesValue = false and - t = node2.getDataFlowType() - ) and - node1 != node2 and - cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) - or - exists(NodeEx mid | - localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, - pragma[only_bind_into](config), cc) and - localFlowStepNodeCand1(mid, node2, config) and - not mid instanceof FlowCheckNode and - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - ) - or - exists(NodeEx mid | - localFlowStepPlus(node1, state, mid, _, _, pragma[only_bind_into](config), cc) and - additionalLocalFlowStepNodeCand2(mid, state, node2, state, config) and - not mid instanceof FlowCheckNode and - preservesValue = false and - t = node2.getDataFlowType() - ) - ) - } - - /** - * Holds if `node1` can step to `node2` in one or more local steps and this - * path can occur as a maximal subsequence of local steps in a dataflow path. - */ - pragma[nomagic] - predicate localFlowBigStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - DataFlowType t, Configuration config, LocalCallContext callContext - ) { - localFlowStepPlus(node1, state1, node2, preservesValue, t, config, callContext) and - localFlowExit(node2, state1, config) and - state1 = state2 - or - additionalLocalFlowStepNodeCand2(node1, state1, node2, state2, config) and - state1 != state2 and - preservesValue = false and - t = node2.getDataFlowType() and - callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) - } -} - -private import LocalFlowBigStep - -private module Stage3Param implements MkStage::StageParam { - private module PrevStage = Stage2; - - class Ap = ApproxAccessPathFront; - - class ApNil = ApproxAccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } - - class ApHeadContent = ContentApprox; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - predicate projectToHeadContent = getContentApprox/1; - - class ApOption = ApproxAccessPathFrontOption; - - ApOption apNone() { result = TApproxAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } - - import BooleanCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApproxAccessPathFrontNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand2/6; - - predicate flowIntoCall = flowIntoCallNodeCand2/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getAHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage3 implements StageSig { - import MkStage::Stage -} - -private module Stage4Param implements MkStage::StageParam { - private module PrevStage = Stage3; - - class Ap = AccessPathFront; - - class ApNil = AccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathFrontOption; - - ApOption apNone() { result = TAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } - - import BooleanCallContext - - pragma[nomagic] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) and - exists(lcc) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clearSet(NodeEx node, ContentSet c, Configuration config) { - PrevStage::revFlow(node, config) and - clearsContentCached(node.asNode(), c) - } - - pragma[nomagic] - private predicate clearContent(NodeEx node, Content c, Configuration config) { - exists(ContentSet cs | - PrevStage::readStepCand(_, pragma[only_bind_into](c), _, pragma[only_bind_into](config)) and - c = cs.getAReadContent() and - clearSet(node, cs, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clear(NodeEx node, Ap ap, Configuration config) { - clearContent(node, ap.getHead().getContent(), config) - } - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - not clear(node, ap, config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage4 implements StageSig { - import MkStage::Stage -} - -/** - * Holds if `argApf` is recorded as the summary context for flow reaching `node` - * and remains relevant for the following pruning stage. - */ -private predicate flowCandSummaryCtx( - NodeEx node, FlowState state, AccessPathFront argApf, Configuration config -) { - exists(AccessPathFront apf | - Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf, config) and - Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf, - config) - ) -} - -/** - * Holds if a length 2 access path approximation with the head `tc` is expected - * to be expensive. - */ -private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { - exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf, config)) and - nodes = - strictcount(NodeEx n, FlowState state | - Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - or - flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - ) and - accessPathApproxCostLimits(apLimit, tupleLimit) and - apLimit < tails and - tupleLimit < (tails - 1) * nodes and - not tc.forceHighPrecision() - ) -} - -private newtype TAccessPathApprox = - TNil(DataFlowType t) or - TConsNil(TypedContent tc, DataFlowType t) { - Stage4::consCand(tc, TFrontNil(t), _) and - not expensiveLen2unfolding(tc, _) - } or - TConsCons(TypedContent tc1, TypedContent tc2, int len) { - Stage4::consCand(tc1, TFrontHead(tc2), _) and - len in [2 .. accessPathLimit()] and - not expensiveLen2unfolding(tc1, _) - } or - TCons1(TypedContent tc, int len) { - len in [1 .. accessPathLimit()] and - expensiveLen2unfolding(tc, _) - } - -/** - * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only - * the first two elements of the list and its length are tracked. If data flows - * from a source to a given node with a given `AccessPathApprox`, this indicates - * the sequence of dereference operations needed to get from the value in the node - * to the tracked object. The final type indicates the type of the tracked object. - */ -abstract private class AccessPathApprox extends TAccessPathApprox { - abstract string toString(); - - abstract TypedContent getHead(); - - abstract int len(); - - abstract DataFlowType getType(); - - abstract AccessPathFront getFront(); - - /** Gets the access path obtained by popping `head` from this path, if any. */ - abstract AccessPathApprox pop(TypedContent head); -} - -private class AccessPathApproxNil extends AccessPathApprox, TNil { - private DataFlowType t; - - AccessPathApproxNil() { this = TNil(t) } - - override string toString() { result = concat(": " + ppReprType(t)) } - - override TypedContent getHead() { none() } - - override int len() { result = 0 } - - override DataFlowType getType() { result = t } - - override AccessPathFront getFront() { result = TFrontNil(t) } - - override AccessPathApprox pop(TypedContent head) { none() } -} - -abstract private class AccessPathApproxCons extends AccessPathApprox { } - -private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { - private TypedContent tc; - private DataFlowType t; - - AccessPathApproxConsNil() { this = TConsNil(tc, t) } - - override string toString() { - // The `concat` becomes "" if `ppReprType` has no result. - result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) - } - - override TypedContent getHead() { result = tc } - - override int len() { result = 1 } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } -} - -private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { - private TypedContent tc1; - private TypedContent tc2; - private int len; - - AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - - override string toString() { - if len = 2 - then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" - else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc1 } - - override int len() { result = len } - - override DataFlowType getType() { result = tc1.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc1) } - - override AccessPathApprox pop(TypedContent head) { - head = tc1 and - ( - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - } -} - -private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { - private TypedContent tc; - private int len; - - AccessPathApproxCons1() { this = TCons1(tc, len) } - - override string toString() { - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc } - - override int len() { result = len } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { - head = tc and - ( - exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2), _) | - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - or - exists(DataFlowType t | - len = 1 and - Stage4::consCand(tc, TFrontNil(t), _) and - result = TNil(t) - ) - ) - } -} - -/** Gets the access path obtained by popping `tc` from `ap`, if any. */ -private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } - -/** Gets the access path obtained by pushing `tc` onto `ap`. */ -private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } - -private newtype TAccessPathApproxOption = - TAccessPathApproxNone() or - TAccessPathApproxSome(AccessPathApprox apa) - -private class AccessPathApproxOption extends TAccessPathApproxOption { - string toString() { - this = TAccessPathApproxNone() and result = "" - or - this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) - } -} - -private module Stage5Param implements MkStage::StageParam { - private module PrevStage = Stage4; - - class Ap = AccessPathApprox; - - class ApNil = AccessPathApproxNil; - - pragma[nomagic] - PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathApproxOption; - - ApOption apNone() { result = TAccessPathApproxNone() } - - ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } - - import Level1CallContext - import LocalCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, lcc) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { any() } - - // Type checking is not necessary here as it has already been done in stage 3. - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage5 = MkStage::Stage; - -bindingset[conf, result] -private Configuration unbindConf(Configuration conf) { - exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c)) -} - -pragma[nomagic] -private predicate nodeMayUseSummary0( - NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(AccessPathApprox apa0 | - Stage5::parameterMayFlowThrough(p, _, _) and - Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0, config) and - Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), - TAccessPathApproxSome(apa), apa0, config) - ) -} - -pragma[nomagic] -private predicate nodeMayUseSummary( - NodeEx n, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::parameterMayFlowThrough(p, apa, config) and - nodeMayUseSummary0(n, p, state, apa, config) - ) -} - -private newtype TSummaryCtx = - TSummaryCtxNone() or - TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { - exists(Configuration config | - Stage5::parameterMayFlowThrough(p, ap.getApprox(), config) and - Stage5::revFlow(p, state, _, config) - ) - } - -/** - * A context for generating flow summaries. This represents flow entry through - * a specific parameter with an access path of a specific shape. - * - * Summaries are only created for parameters that may flow through. - */ -abstract private class SummaryCtx extends TSummaryCtx { - abstract string toString(); -} - -/** A summary context from which no flow summary can be generated. */ -private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { - override string toString() { result = "" } -} - -/** A summary context from which a flow summary can be generated. */ -private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParamNodeEx p; - private FlowState s; - private AccessPath ap; - - SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } - - ParameterPosition getParameterPos() { p.isParameterOf(_, result) } - - ParamNodeEx getParamNode() { result = p } - - override string toString() { result = p + ": " + ap } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** - * Gets the number of length 2 access path approximations that correspond to `apa`. - */ -private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { - exists(TypedContent tc, int len | - tc = apa.getHead() and - len = apa.len() and - result = - strictcount(AccessPathFront apf | - Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), - config) - ) - ) -} - -private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = - strictcount(NodeEx n, FlowState state | - Stage5::revFlow(n, state, apa, config) or nodeMayUseSummary(n, state, apa, config) - ) -} - -/** - * Holds if a length 2 access path approximation matching `apa` is expected - * to be expensive. - */ -private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = count1to2unfold(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - apLimit < aps and - tupleLimit < (aps - 1) * nodes - ) -} - -private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { - exists(TypedContent head | - apa.pop(head) = result and - Stage5::consCand(head, result, config) - ) -} - -/** - * Holds with `unfold = false` if a precise head-tail representation of `apa` is - * expected to be expensive. Holds with `unfold = true` otherwise. - */ -private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { - if apa.getHead().forceHighPrecision() - then unfold = true - else - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = countPotentialAps(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true - ) -} - -/** - * Gets the number of `AccessPath`s that correspond to `apa`. - */ -pragma[assume_small_delta] -private int countAps(AccessPathApprox apa, Configuration config) { - evalUnfold(apa, false, config) and - result = 1 and - (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) - or - evalUnfold(apa, false, config) and - result = count1to2unfold(apa, config) and - not expensiveLen1to2unfolding(apa, config) - or - evalUnfold(apa, true, config) and - result = countPotentialAps(apa, config) -} - -/** - * Gets the number of `AccessPath`s that would correspond to `apa` assuming - * that it is expanded to a precise head-tail representation. - */ -language[monotonicAggregates] -pragma[assume_small_delta] -private int countPotentialAps(AccessPathApprox apa, Configuration config) { - apa instanceof AccessPathApproxNil and result = 1 - or - result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) -} - -private newtype TAccessPath = - TAccessPathNil(DataFlowType t) or - TAccessPathCons(TypedContent head, AccessPath tail) { - exists(AccessPathApproxCons apa | - not evalUnfold(apa, false, _) and - head = apa.getHead() and - tail.getApprox() = getATail(apa, _) - ) - } or - TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - not expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head1 = apa.getHead() and - head2 = getATail(apa, _).getHead() - ) - } or - TAccessPathCons1(TypedContent head, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head = apa.getHead() - ) - } - -private newtype TPathNode = - pragma[assume_small_delta] - TPathNodeMid( - NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config - ) { - // A PathNode is introduced by a source ... - Stage5::revFlow(node, state, config) and - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - or - // ... or a step from an existing PathNode to another node. - exists(PathNodeMid mid | - pathStep(mid, node, state, cc, sc, ap) and - pragma[only_bind_into](config) = mid.getConfiguration() and - Stage5::revFlow(node, state, ap.getApprox(), pragma[only_bind_into](config)) - ) - } or - TPathNodeSink(NodeEx node, FlowState state, Configuration config) { - exists(PathNodeMid sink | - sink.isAtSink() and - node = sink.getNodeEx() and - state = sink.getState() and - config = sink.getConfiguration() - ) - } or - TPathNodeSourceGroup(string sourceGroup, Configuration config) { - exists(PathNodeImpl source | - sourceGroup = source.getSourceGroup() and - config = source.getConfiguration() - ) - } or - TPathNodeSinkGroup(string sinkGroup, Configuration config) { - exists(PathNodeSink sink | - sinkGroup = sink.getSinkGroup() and - config = sink.getConfiguration() - ) - } - -/** - * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a - * source to a given node with a given `AccessPath`, this indicates the sequence - * of dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ -private class AccessPath extends TAccessPath { - /** Gets the head of this access path, if any. */ - abstract TypedContent getHead(); - - /** Gets the tail of this access path, if any. */ - abstract AccessPath getTail(); - - /** Gets the front of this access path. */ - abstract AccessPathFront getFront(); - - /** Gets the approximation of this access path. */ - abstract AccessPathApprox getApprox(); - - /** Gets the length of this access path. */ - abstract int length(); - - /** Gets a textual representation of this access path. */ - abstract string toString(); - - /** Gets the access path obtained by popping `tc` from this access path, if any. */ - final AccessPath pop(TypedContent tc) { - result = this.getTail() and - tc = this.getHead() - } - - /** Gets the access path obtained by pushing `tc` onto this access path. */ - final AccessPath push(TypedContent tc) { this = result.pop(tc) } -} - -private class AccessPathNil extends AccessPath, TAccessPathNil { - private DataFlowType t; - - AccessPathNil() { this = TAccessPathNil(t) } - - DataFlowType getType() { result = t } - - override TypedContent getHead() { none() } - - override AccessPath getTail() { none() } - - override AccessPathFrontNil getFront() { result = TFrontNil(t) } - - override AccessPathApproxNil getApprox() { result = TNil(t) } - - override int length() { result = 0 } - - override string toString() { result = concat(": " + ppReprType(t)) } -} - -private class AccessPathCons extends AccessPath, TAccessPathCons { - private TypedContent head; - private AccessPath tail; - - AccessPathCons() { this = TAccessPathCons(head, tail) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { result = tail } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - pragma[assume_small_delta] - override AccessPathApproxCons getApprox() { - result = TConsNil(head, tail.(AccessPathNil).getType()) - or - result = TConsCons(head, tail.getHead(), this.length()) - or - result = TCons1(head, this.length()) - } - - pragma[assume_small_delta] - override int length() { result = 1 + tail.length() } - - private string toStringImpl(boolean needsSuffix) { - exists(DataFlowType t | - tail = TAccessPathNil(t) and - needsSuffix = false and - result = head.toString() + "]" + concat(" : " + ppReprType(t)) - ) - or - result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) - or - exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | - result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true - or - result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false - ) - or - exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | - result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true - or - result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false - ) - } - - override string toString() { - result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" - or - result = "[" + this.toStringImpl(false) - } -} - -private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { - private TypedContent head1; - private TypedContent head2; - private int len; - - AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } - - override TypedContent getHead() { result = head1 } - - override AccessPath getTail() { - Stage5::consCand(head1, result.getApprox(), _) and - result.getHead() = head2 and - result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head1) } - - override AccessPathApproxCons getApprox() { - result = TConsCons(head1, head2, len) or - result = TCons1(head1, len) - } - - override int length() { result = len } - - override string toString() { - if len = 2 - then result = "[" + head1.toString() + ", " + head2.toString() + "]" - else - result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" - } -} - -private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { - private TypedContent head; - private int len; - - AccessPathCons1() { this = TAccessPathCons1(head, len) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { - Stage5::consCand(head, result.getApprox(), _) and result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - - override int length() { result = len } - - override string toString() { - if len = 1 - then result = "[" + head.toString() + "]" - else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" - } -} - -abstract private class PathNodeImpl extends TPathNode { - /** Gets the `FlowState` of this node. */ - abstract FlowState getState(); - - /** Gets the associated configuration. */ - abstract Configuration getConfiguration(); - - /** Holds if this node is a source. */ - abstract predicate isSource(); - - abstract PathNodeImpl getASuccessorImpl(); - - private PathNodeImpl getASuccessorIfHidden() { - this.isHidden() and - result = this.getASuccessorImpl() - } - - pragma[nomagic] - private PathNodeImpl getANonHiddenSuccessor0() { - result = this.getASuccessorIfHidden*() and - not result.isHidden() - } - - final PathNodeImpl getANonHiddenSuccessor() { - result = this.getASuccessorImpl().getANonHiddenSuccessor0() and - not this.isHidden() - } - - abstract NodeEx getNodeEx(); - - predicate isHidden() { - not this.getConfiguration().includeHiddenNodes() and - ( - hiddenNode(this.getNodeEx().asNode()) and - not this.isSource() and - not this instanceof PathNodeSink - or - this.getNodeEx() instanceof TNodeImplicitRead - ) - } - - string getSourceGroup() { - this.isSource() and - this.getConfiguration().sourceGrouping(this.getNodeEx().asNode(), result) + not singleConfiguration() and + getConfig(state1).isAdditionalFlowStep(node1, node2) and + state2 = state1 } - predicate isFlowSource() { - this.isSource() and not exists(this.getSourceGroup()) - or - this instanceof PathNodeSourceGroup + predicate allowImplicitRead(Node node, ContentSet c) { + any(Configuration config).allowImplicitRead(node, c) } - predicate isFlowSink() { - this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or - this instanceof PathNodeSinkGroup - } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } - private string ppAp() { - this instanceof PathNodeSink and result = "" - or - exists(string s | s = this.(PathNodeMid).getAp().toString() | - if s = "" then result = "" else result = " " + s - ) - } + FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } - private string ppCtx() { - this instanceof PathNodeSink and result = "" - or - result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + predicate sourceGrouping(Node source, string sourceGroup) { + any(Configuration config).sourceGrouping(source, sourceGroup) } - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + predicate sinkGrouping(Node sink, string sinkGroup) { + any(Configuration config).sinkGrouping(sink, sinkGroup) } -} - -/** Holds if `n` can reach a sink. */ -private predicate directReach(PathNodeImpl n) { - n instanceof PathNodeSink or - n instanceof PathNodeSinkGroup or - directReach(n.getANonHiddenSuccessor()) -} - -/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ -private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } -/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { - n1.getANonHiddenSuccessor() = n2 and directReach(n2) + predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() } } -private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) +private import Impl as I +import I /** * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. */ -class PathNode instanceof PathNodeImpl { - PathNode() { reach(this) } - +class PathNode instanceof I::PathNode { /** Gets a textual representation of this element. */ final string toString() { result = super.toString() } @@ -3406,1548 +358,39 @@ class PathNode instanceof PathNodeImpl { } /** Gets the underlying `Node`. */ - final Node getNode() { super.getNodeEx().projectToNode() = result } + final Node getNode() { result = super.getNode() } /** Gets the `FlowState` of this node. */ - final FlowState getState() { result = super.getState() } + final FlowState getState() { result = getState(super.getState()) } /** Gets the associated configuration. */ - final Configuration getConfiguration() { result = super.getConfiguration() } + final Configuration getConfiguration() { result = getConfig(super.getState()) } /** Gets a successor of this node, if any. */ - final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } + final PathNode getASuccessor() { result = super.getASuccessor() } /** Holds if this node is a source. */ final predicate isSource() { super.isSource() } /** Holds if this node is a grouping of source nodes. */ - final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group, _) } + final predicate isSourceGroup(string group) { super.isSourceGroup(group) } /** Holds if this node is a grouping of sink nodes. */ - final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group, _) } + final predicate isSinkGroup(string group) { super.isSinkGroup(group) } } -/** - * Provides the query predicates needed to include a graph in a path-problem query. - */ -module PathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } - - /** Holds if `n` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode n, string key, string val) { - key = "semmle.label" and val = n.toString() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { - Subpaths::subpaths(arg, par, ret, out) - } -} - -/** - * An intermediate flow graph node. This is a triple consisting of a `Node`, - * a `CallContext`, and a `Configuration`. - */ -private class PathNodeMid extends PathNodeImpl, TPathNodeMid { - NodeEx node; - FlowState state; - CallContext cc; - SummaryCtx sc; - AccessPath ap; - Configuration config; - - PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - SummaryCtx getSummaryCtx() { result = sc } - - AccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - private PathNodeMid getSuccMid() { - pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx(), result.getAp()) and - result.getConfiguration() = unbindConf(this.getConfiguration()) - } - - override PathNodeImpl getASuccessorImpl() { - // an intermediate step to another intermediate node - result = this.getSuccMid() - or - // a final step to a sink - result = this.getSuccMid().projectToSink() - } - - override predicate isSource() { - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - } - - predicate isAtSink() { - sinkNode(node, state, config) and - ap instanceof AccessPathNil and - if hasSinkCallCtx(config) - then - // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` - // is exactly what we need to check. This also implies - // `sc instanceof SummaryCtxNone`. - // For `FeatureEqualSourceSinkCallContext` the initial call context was - // set to `CallContextSomeCall` and jumps are disallowed, so - // `cc instanceof CallContextNoCall` never holds. On the other hand, - // in this case there's never any need to enter a call except to identify - // a summary, so the condition in `pathIntoCallable` enforces this, which - // means that `sc instanceof SummaryCtxNone` holds if and only if we are - // in the call context of the source. - sc instanceof SummaryCtxNone or - cc instanceof CallContextNoCall - else any() - } - - PathNodeSink projectToSink() { - this.isAtSink() and - result.getNodeEx() = node and - result.getState() = state and - result.getConfiguration() = unbindConf(config) - } -} - -/** - * A flow graph node corresponding to a sink. This is disjoint from the - * intermediate nodes in order to uniquely correspond to a given sink by - * excluding the `CallContext`. - */ -private class PathNodeSink extends PathNodeImpl, TPathNodeSink { - NodeEx node; - FlowState state; - Configuration config; - - PathNodeSink() { this = TPathNodeSink(node, state, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result = TPathNodeSinkGroup(this.getSinkGroup(), config) - } - - override predicate isSource() { sourceNode(node, state, config) } - - string getSinkGroup() { config.sinkGrouping(node.asNode(), result) } -} - -private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { - string sourceGroup; - Configuration config; - - PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result.getSourceGroup() = sourceGroup and - result.getConfiguration() = config - } - - override predicate isSource() { none() } - - override string toString() { result = sourceGroup } - - override 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 - } -} - -private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { - string sinkGroup; - Configuration config; - - PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { none() } - - override predicate isSource() { none() } - - override string toString() { result = sinkGroup } - - override 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 - } -} - -private predicate pathNode( - PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, - Configuration conf, LocalCallContext localCC -) { - midnode = mid.getNodeEx() and - state = mid.getState() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - midnode.getEnclosingCallable()) and - ap = mid.getAp() -} - -/** - * Holds if data may flow from `mid` to `node`. The last step in or out of - * a callable is recorded by `cc`. - */ -pragma[assume_small_delta] -pragma[nomagic] -private predicate pathStep( - PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap -) { - exists(NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC | - pathNode(mid, midnode, state0, cc, sc, ap, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, true, _, conf, localCC) - ) - or - exists( - AccessPath ap0, NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC - | - pathNode(mid, midnode, state0, cc, sc, ap0, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), conf, - localCC) and - ap0 instanceof AccessPathNil - ) - or - jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - ap = mid.getAp() - or - additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, mid.getConfiguration()) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - pathIntoCallable(mid, node, state, _, cc, sc, _, _) and ap = mid.getAp() - or - pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone - or - pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() -} - -pragma[nomagic] -private predicate pathReadStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - tc = ap0.getHead() and - Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -pragma[nomagic] -private predicate pathStoreStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -private predicate pathOutOfCallable0( - PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa, - Configuration config -) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - apa = mid.getAp().getApprox() and - config = mid.getConfiguration() -} - -pragma[nomagic] -private predicate pathOutOfCallable1( - PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPathApprox apa, Configuration config -) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - pathOutOfCallable0(mid, pos, state, innercc, apa, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() +private predicate hasFlow(Node source, Node sink, Configuration config) { + exists(PathNode source0, PathNode sink0 | + hasFlowPath(source0, sink0, config) and + source0.getNode() = source and + sink0.getNode() = sink ) } -pragma[noinline] -private NodeEx getAnOutNodeFlow( - ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config -) { - result.asNode() = kind.getAnOutNode(call) and - Stage5::revFlow(result, _, apa, config) +private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) { + hasFlowPath(source, sink) and source.getConfiguration() = config } -/** - * Holds if data may flow from `mid` to `out`. The last step of this path - * is a return from a callable and is recorded by `cc`, if needed. - */ -pragma[noinline] -private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { - exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | - pathOutOfCallable1(mid, call, kind, state, cc, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} +private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) } -/** - * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. - */ -pragma[noinline] -private predicate pathIntoArg( - PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(ArgNodeEx arg, ArgumentPosition apos | - pathNode(mid, arg, state, cc, _, ap, config, _) and - arg.asNode().(ArgNode).argumentOf(call, apos) and - apa = ap.getApprox() and - parameterMatch(ppos, apos) - ) -} - -pragma[nomagic] -private predicate parameterCand( - DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::revFlow(p, _, apa, config) and - p.isParameterOf(callable, pos) - ) -} - -pragma[nomagic] -private predicate pathIntoCallable0( - PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, AccessPath ap, Configuration config -) { - exists(AccessPathApprox apa | - pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - callable = resolveCall(call, outercc) and - parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa), - pragma[only_bind_into](config)) - ) -} - -/** - * Holds if data may flow from `mid` to `p` through `call`. The contexts - * before and after entering the callable are `outercc` and `innercc`, - * respectively. - */ -pragma[nomagic] -private predicate pathIntoCallable( - PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, - SummaryCtx sc, DataFlowCall call, Configuration config -) { - exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | - pathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - ( - sc = TSummaryCtxSome(p, state, ap) - or - not exists(TSummaryCtxSome(p, state, ap)) and - sc = TSummaryCtxNone() and - // When the call contexts of source and sink needs to match then there's - // never any reason to enter a callable except to find a summary. See also - // the comment in `PathNodeMid::isAtSink`. - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) -} - -/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ -pragma[nomagic] -private predicate paramFlowsThrough( - ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, - AccessPathApprox apa, Configuration config -) { - exists(RetNodeEx ret | - pathNode(_, ret, state, cc, sc, ap, config, _) and - kind = ret.getKind() and - apa = ap.getApprox() and - parameterFlowThroughAllowed(sc.getParamNode(), kind) - ) -} - -pragma[nomagic] -private predicate pathThroughCallable0( - DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(CallContext innercc, SummaryCtx sc | - pathIntoCallable(mid, _, _, cc, innercc, sc, call, config) and - paramFlowsThrough(kind, state, innercc, sc, ap, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` through a callable to the node `out`. - * The context `cc` is restored to its value prior to entering the callable. - */ -pragma[noinline] -private predicate pathThroughCallable( - PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap -) { - exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config | - pathThroughCallable0(call, mid, kind, state, cc, ap, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -private module Subpaths { - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths01( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - exists(Configuration config | - pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and - pathIntoCallable(arg, par, _, _, innercc, sc, _, config) and - paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, - pragma[only_bind_into](apout), _, unbindConf(config)) and - not arg.isHidden() - ) - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `sout`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths02( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and - out.asNode() = kind.getAnOutNode(_) - } - - pragma[nomagic] - private Configuration getPathNodeConf(PathNodeImpl n) { result = n.getConfiguration() } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple. - */ - pragma[nomagic] - private predicate subpaths03( - PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, AccessPath apout - ) { - exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | - subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and - pathNode(ret, retnode, sout, innercc, sc, apout, unbindConf(getPathNodeConf(arg)), _) and - kind = retnode.getKind() - ) - } - - private PathNodeImpl localStepToHidden(PathNodeImpl n) { - n.getASuccessorImpl() = result and - result.isHidden() and - exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | - localFlowBigStep(n1, _, n2, _, _, _, _, _) or - store(n1, _, n2, _, _) or - readSet(n1, _, n2, _) - ) - } - - pragma[nomagic] - private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { - succ = pred.getANonHiddenSuccessor() and - succNode = succ.getNodeEx() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { - exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | - pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and - subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and - hasSuccessor(pragma[only_bind_into](arg), par, p) and - not ret.isHidden() and - pathNode(out0, o, sout, _, _, apout, _, _) - | - out = out0 or out = out0.projectToSink() - ) - } - - /** - * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. - */ - predicate retReach(PathNodeImpl n) { - exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) - or - exists(PathNodeImpl mid | - retReach(mid) and - n.getANonHiddenSuccessor() = mid and - not subpaths(_, mid, _, _) - ) - } -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -private predicate hasFlowPath( - PathNodeImpl flowsource, PathNodeImpl flowsink, Configuration configuration -) { - flowsource.isFlowSource() and - flowsource.getConfiguration() = configuration and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.isFlowSink() -} - -private predicate flowsTo( - PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink, - Configuration configuration -) { - flowsource.isSource() and - flowsource.getConfiguration() = configuration and - flowsource.getNodeEx().asNode() = source and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.getNodeEx().asNode() = sink -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -predicate flowsTo(Node source, Node sink, Configuration configuration) { - flowsTo(_, _, source, sink, configuration) -} - -private predicate finalStats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples -) { - fwd = true and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and - tuples = count(PathNodeImpl pn) - or - fwd = false and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and - tuples = count(PathNode pn) -} - -/** - * INTERNAL: Only for debugging. - * - * Calculates per-stage metrics for data flow. - */ -predicate stageStats( - int n, string stage, int nodes, int fields, int conscand, int states, int tuples, - Configuration config -) { - stage = "1 Fwd" and - n = 10 and - Stage1::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "1 Rev" and - n = 15 and - Stage1::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Fwd" and - n = 20 and - Stage2::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Rev" and - n = 25 and - Stage2::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Fwd" and - n = 30 and - Stage3::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Rev" and - n = 35 and - Stage3::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Fwd" and - n = 40 and - Stage4::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Rev" and - n = 45 and - Stage4::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Fwd" and - n = 50 and - Stage5::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Rev" and - n = 55 and - Stage5::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) - or - stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) -} - -private module FlowExploration { - private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - jumpStep(node1, node2, config) - or - additionalJumpStep(node1, node2, config) - or - additionalJumpStateStep(node1, _, node2, _, config) - or - // flow into callable - viableParamArgEx(_, node2, node1) - or - // flow out of a callable - viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) - | - c1 = node1.getEnclosingCallable() and - c2 = node2.getEnclosingCallable() and - c1 != c2 - ) - } - - private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSource(n) or config.isSource(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSrc(mid, config) and callableStep(mid, c, config) - ) - } - - private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSink(n) or config.isSink(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSink(mid, config) and callableStep(c, mid, config) - ) - } - - private newtype TCallableExt = - TCallable(DataFlowCallable c, Configuration config) { - interestingCallableSrc(c, config) or - interestingCallableSink(c, config) - } or - TCallableSrc() or - TCallableSink() - - private predicate callableExtSrc(TCallableSrc src) { any() } - - private predicate callableExtSink(TCallableSink sink) { any() } - - private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { - exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | - callableStep(c1, c2, config) and - ce1 = TCallable(c1, pragma[only_bind_into](config)) and - ce2 = TCallable(c2, pragma[only_bind_into](config)) - ) - or - exists(Node n, Configuration config | - ce1 = TCallableSrc() and - (config.isSource(n) or config.isSource(n, _)) and - ce2 = TCallable(getNodeEnclosingCallable(n), config) - ) - or - exists(Node n, Configuration config | - ce2 = TCallableSink() and - (config.isSink(n) or config.isSink(n, _)) and - ce1 = TCallable(getNodeEnclosingCallable(n), config) - ) - } - - private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { - callableExtStepFwd(ce2, ce1) - } - - private int distSrcExt(TCallableExt c) = - shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) - - private int distSinkExt(TCallableExt c) = - shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) - - private int distSrc(DataFlowCallable c, Configuration config) { - result = distSrcExt(TCallable(c, config)) - 1 - } - - private int distSink(DataFlowCallable c, Configuration config) { - result = distSinkExt(TCallable(c, config)) - 1 - } - - private newtype TPartialAccessPath = - TPartialNil(DataFlowType t) or - TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first - * element of the list and its length are tracked. If data flows from a source to - * a given node with a given `AccessPath`, this indicates the sequence of - * dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ - private class PartialAccessPath extends TPartialAccessPath { - abstract string toString(); - - TypedContent getHead() { this = TPartialCons(result, _) } - - int len() { - this = TPartialNil(_) and result = 0 - or - this = TPartialCons(_, result) - } - - DataFlowType getType() { - this = TPartialNil(result) - or - exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) - } - } - - private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { - override string toString() { - exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) - } - } - - private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { - override string toString() { - exists(TypedContent tc, int len | this = TPartialCons(tc, len) | - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private newtype TRevPartialAccessPath = - TRevPartialNil() or - TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `Content`s, but only the first - * element of the list and its length are tracked. - */ - private class RevPartialAccessPath extends TRevPartialAccessPath { - abstract string toString(); - - Content getHead() { this = TRevPartialCons(result, _) } - - int len() { - this = TRevPartialNil() and result = 0 - or - this = TRevPartialCons(_, result) - } - } - - private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { - override string toString() { result = "" } - } - - private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { - override string toString() { - exists(Content c, int len | this = TRevPartialCons(c, len) | - if len = 1 - then result = "[" + c.toString() + "]" - else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private predicate relevantState(FlowState state) { - sourceNode(_, state, _) or - sinkNode(_, state, _) or - additionalLocalStateStep(_, state, _, _, _) or - additionalLocalStateStep(_, _, _, state, _) or - additionalJumpStateStep(_, state, _, _, _) or - additionalJumpStateStep(_, _, _, state, _) - } - - private newtype TSummaryCtx1 = - TSummaryCtx1None() or - TSummaryCtx1Param(ParamNodeEx p) - - private newtype TSummaryCtx2 = - TSummaryCtx2None() or - TSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TSummaryCtx3 = - TSummaryCtx3None() or - TSummaryCtx3Some(PartialAccessPath ap) - - private newtype TRevSummaryCtx1 = - TRevSummaryCtx1None() or - TRevSummaryCtx1Some(ReturnPosition pos) - - private newtype TRevSummaryCtx2 = - TRevSummaryCtx2None() or - TRevSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TRevSummaryCtx3 = - TRevSummaryCtx3None() or - TRevSummaryCtx3Some(RevPartialAccessPath ap) - - private newtype TPartialPathNode = - TPartialPathNodeFwd( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = TPartialNil(node.getDataFlowType()) and - exists(config.explorationLimit()) - or - partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap, config) and - distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() - } or - TPartialPathNodeRev( - NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, - RevPartialAccessPath ap, Configuration config - ) { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() and - exists(config.explorationLimit()) - or - revPartialPathStep(_, node, state, sc1, sc2, sc3, ap, config) and - not clearsContentEx(node, ap.getHead()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead()) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() - } - - pragma[nomagic] - private predicate partialPathNodeMk0( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap, config) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - not clearsContentEx(node, ap.getHead().getContent()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead().getContent()) - ) and - if node.asNode() instanceof CastingNode - then compatibleTypes(node.getDataFlowType(), ap.getType()) - else any() - } - - /** - * A `Node` augmented with a call context, an access path, and a configuration. - */ - class PartialPathNode extends TPartialPathNode { - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() - } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - /** Gets the underlying `Node`. */ - final Node getNode() { this.getNodeEx().projectToNode() = result } - - FlowState getState() { none() } - - private NodeEx getNodeEx() { - result = this.(PartialPathNodeFwd).getNodeEx() or - result = this.(PartialPathNodeRev).getNodeEx() - } - - /** Gets the associated configuration. */ - Configuration getConfiguration() { none() } - - /** Gets a successor of this node, if any. */ - PartialPathNode getASuccessor() { none() } - - /** - * Gets the approximate distance to the nearest source measured in number - * of interprocedural steps. - */ - int getSourceDistance() { - result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - /** - * Gets the approximate distance to the nearest sink measured in number - * of interprocedural steps. - */ - int getSinkDistance() { - result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - private string ppAp() { - exists(string s | - s = this.(PartialPathNodeFwd).getAp().toString() or - s = this.(PartialPathNodeRev).getAp().toString() - | - if s = "" then result = "" else result = " " + s - ) - } - - private string ppCtx() { - result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" - } - - /** Holds if this is a source in a forward-flow path. */ - predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } - - /** Holds if this is a sink in a reverse-flow path. */ - predicate isRevSink() { this.(PartialPathNodeRev).isSink() } - } - - /** - * Provides the query predicates needed to include a graph in a path-problem query. - */ - module PartialPathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } - } - - private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { - NodeEx node; - FlowState state; - CallContext cc; - TSummaryCtx1 sc1; - TSummaryCtx2 sc2; - TSummaryCtx3 sc3; - PartialAccessPath ap; - Configuration config; - - PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - TSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TSummaryCtx3 getSummaryCtx3() { result = sc3 } - - PartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeFwd getASuccessor() { - partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp(), - result.getConfiguration()) - } - - predicate isSource() { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap instanceof TPartialNil - } - } - - private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { - NodeEx node; - FlowState state; - TRevSummaryCtx1 sc1; - TRevSummaryCtx2 sc2; - TRevSummaryCtx3 sc3; - RevPartialAccessPath ap; - Configuration config; - - PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } - - RevPartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeRev getASuccessor() { - revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), - this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp(), this.getConfiguration()) - } - - predicate isSink() { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() - } - } - - private predicate partialPathStep( - PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, - TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and - ( - localFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - ) - or - jumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - partialPathStoreStep(mid, _, _, node, ap) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(PartialAccessPath ap0, TypedContent tc | - partialPathReadStep(mid, ap0, tc, node, cc, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsFwd(ap, tc, ap0, config) - ) - or - partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap, config) - or - partialPathOutOfCallable(mid, node, state, cc, ap, config) and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() - or - partialPathThroughCallable(mid, node, state, cc, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - bindingset[result, i] - private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } - - pragma[inline] - private predicate partialPathStoreStep( - PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, - PartialAccessPath ap2 - ) { - exists(NodeEx midNode, DataFlowType contentType | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - store(midNode, tc, node, contentType, mid.getConfiguration()) and - ap2.getHead() = tc and - ap2.len() = unbindInt(ap1.len() + 1) and - compatibleTypes(ap1.getType(), contentType) - ) - } - - pragma[nomagic] - private predicate apConsFwd( - PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeFwd mid | - partialPathStoreStep(mid, ap1, tc, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate partialPathReadStep( - PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc, - Configuration config - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and - ap.getHead() = tc and - pragma[only_bind_into](config) = mid.getConfiguration() and - cc = mid.getCallContext() - ) - } - - private predicate partialPathOutOfCallable0( - PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, - PartialAccessPath ap, Configuration config - ) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - ap = mid.getAp() and - config = mid.getConfiguration() - } - - pragma[nomagic] - private predicate partialPathOutOfCallable1( - PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - partialPathOutOfCallable0(mid, pos, state, innercc, ap, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) - } - - private predicate partialPathOutOfCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(ReturnKindExt kind, DataFlowCall call | - partialPathOutOfCallable1(mid, call, kind, state, cc, ap, config) - | - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[noinline] - private predicate partialPathIntoArg( - PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ArgNode arg, ArgumentPosition apos | - arg = mid.getNodeEx().asNode() and - state = mid.getState() and - cc = mid.getCallContext() and - arg.argumentOf(call, apos) and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate partialPathIntoCallable0( - PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - partialPathIntoArg(mid, pos, state, outercc, call, ap, config) and - callable = resolveCall(call, outercc) - } - - private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, - CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ParameterPosition pos, DataFlowCallable callable | - partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - sc1 = TSummaryCtx1Param(p) and - sc2 = TSummaryCtx2Some(state) and - sc3 = TSummaryCtx3Some(ap) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) - } - - pragma[nomagic] - private predicate paramFlowsThroughInPartialPath( - ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeFwd mid, RetNodeEx ret | - mid.getNodeEx() = ret and - kind = ret.getKind() and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() and - ap = mid.getAp() - ) - } - - pragma[noinline] - private predicate partialPathThroughCallable0( - DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | - partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _, config) and - paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap, config) - ) - } - - private predicate partialPathThroughCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ReturnKindExt kind | - partialPathThroughCallable0(call, mid, kind, state, cc, ap, config) and - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[nomagic] - private predicate revPartialPathStep( - PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, - TRevSummaryCtx3 sc3, RevPartialAccessPath ap, Configuration config - ) { - localFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - jumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - revPartialPathReadStep(mid, _, _, node, ap) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(RevPartialAccessPath ap0, Content c | - revPartialPathStoreStep(mid, ap0, c, node, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsRev(ap, c, ap0, config) - ) - or - exists(ParamNodeEx p | - mid.getNodeEx() = p and - viableParamArgEx(_, p, node) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - or - exists(ReturnPosition pos | - revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap, config) and - pos = getReturnPosition(node.asNode()) - ) - or - revPartialPathThroughCallable(mid, node, state, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - pragma[inline] - private predicate revPartialPathReadStep( - PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, - RevPartialAccessPath ap2 - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - read(node, c, midNode, mid.getConfiguration()) and - ap2.getHead() = c and - ap2.len() = unbindInt(ap1.len() + 1) - ) - } - - pragma[nomagic] - private predicate apConsRev( - RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeRev mid | - revPartialPathReadStep(mid, ap1, c, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathStoreStep( - PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config - ) { - exists(NodeEx midNode, TypedContent tc | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - store(node, tc, midNode, _, config) and - ap.getHead() = c and - config = mid.getConfiguration() and - tc.getContent() = c - ) - } - - pragma[nomagic] - private predicate revPartialPathIntoReturn( - PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, - TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap, - Configuration config - ) { - exists(NodeEx out | - mid.getNodeEx() = out and - mid.getState() = state and - viableReturnPosOutEx(call, pos, out) and - sc1 = TRevSummaryCtx1Some(pos) and - sc2 = TRevSummaryCtx2Some(state) and - sc3 = TRevSummaryCtx3Some(ap) and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathFlowsThrough( - ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, - TRevSummaryCtx3Some sc3, RevPartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | - mid.getNodeEx() = p and - mid.getState() = state and - p.getPosition() = ppos and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable0( - DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, - RevPartialAccessPath ap, Configuration config - ) { - exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | - revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _, config) and - revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap, config) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ArgumentPosition pos | - revPartialPathThroughCallable0(call, mid, pos, state, ap, config) and - node.asNode().(ArgNode).argumentOf(call, pos) - ) - } -} - -import FlowExploration - -private predicate partialFlow( - PartialPathNode source, PartialPathNode node, Configuration configuration -) { - source.getConfiguration() = configuration and - source.isFwdSource() and - node = source.getASuccessor+() -} - -private predicate revPartialFlow( - PartialPathNode node, PartialPathNode sink, Configuration configuration -) { - sink.getConfiguration() = configuration and - sink.isRevSink() and - node.getASuccessor+() = sink -} +predicate flowsTo = hasFlow/3; diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll index 1b969756b09..e6bdc74cceb 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll @@ -1,16 +1,16 @@ /** - * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from - * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed - * through the `Configuration` class. This file exists in several identical - * copies, allowing queries to use multiple `Configuration` classes that depend - * on each other without introducing mutual recursion among those configurations. + * DEPRECATED: Use `Make` and `MakeWithState` instead. + * + * Provides a `Configuration` class backwards-compatible interface to the data + * flow library. */ private import DataFlowImplCommon private import DataFlowImplSpecific::Private import DataFlowImplSpecific::Public +private import DataFlowImpl import DataFlowImplCommonPublic +import FlowStateString /** * A configuration of interprocedural data flow analysis. This defines @@ -144,6 +144,10 @@ abstract class Configuration extends string { * - `FeatureEqualSourceSinkCallContext`: * Implies both of the above and additionally ensures that the entire flow * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. */ FlowFeature getAFeature() { none() } @@ -156,7 +160,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from `source` to `sink` for this configuration. */ - predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } + predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) } /** * Holds if data may flow from `source` to `sink` for this configuration. @@ -169,9 +173,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from some source to `sink` for this configuration. */ - predicate hasFlowTo(Node sink) { - sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode() - } + predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) } /** * Holds if data may flow from some source to `sink` for this configuration. @@ -179,10 +181,12 @@ abstract class Configuration extends string { predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } /** + * DEPRECATED: Use `FlowExploration` instead. + * * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` * measured in approximate number of interprocedural steps. */ - int explorationLimit() { none() } + deprecated int explorationLimit() { none() } /** * Holds if hidden nodes should be included in the data flow graph. @@ -191,49 +195,6 @@ abstract class Configuration extends string { * is not visualized (for example in a `path-problem` query). */ predicate includeHiddenNodes() { none() } - - /** - * Holds if there is a partial data flow path from `source` to `node`. The - * approximate distance between `node` and the closest source is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards sink definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sources is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - */ - final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { - partialFlow(source, node, this) and - dist = node.getSourceDistance() - } - - /** - * Holds if there is a partial data flow path from `node` to `sink`. The - * approximate distance between `node` and the closest sink is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards source definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sinks is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - * - * Note that reverse flow has slightly lower precision than the corresponding - * forward flow, as reverse flow disregards type pruning among other features. - */ - final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { - revPartialFlow(node, sink, this) and - dist = node.getSinkDistance() - } } /** @@ -263,90 +224,6 @@ abstract private class ConfigurationRecursionPrevention extends Configuration { } } -private newtype TNodeEx = - TNodeNormal(Node n) or - TNodeImplicitRead(Node n, boolean hasRead) { - any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true] - } - -private class NodeEx extends TNodeEx { - string toString() { - result = this.asNode().toString() - or - exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") - } - - Node asNode() { this = TNodeNormal(result) } - - predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } - - Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } - - pragma[nomagic] - private DataFlowCallable getEnclosingCallable0() { - nodeEnclosingCallable(this.projectToNode(), result) - } - - pragma[inline] - DataFlowCallable getEnclosingCallable() { - pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) - } - - pragma[nomagic] - private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } - - pragma[inline] - DataFlowType getDataFlowType() { - pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) - } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -private class ArgNodeEx extends NodeEx { - ArgNodeEx() { this.asNode() instanceof ArgNode } -} - -private class ParamNodeEx extends NodeEx { - ParamNodeEx() { this.asNode() instanceof ParamNode } - - predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { - this.asNode().(ParamNode).isParameterOf(c, pos) - } - - ParameterPosition getPosition() { this.isParameterOf(_, result) } -} - -private class RetNodeEx extends NodeEx { - RetNodeEx() { this.asNode() instanceof ReturnNodeExt } - - ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } - - ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } -} - -private predicate inBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierIn(n) - | - config.isSource(n) or config.isSource(n, _) - ) -} - -private predicate outBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierOut(n) - | - config.isSink(n) or config.isSink(n, _) - ) -} - /** A bridge class to access the deprecated `isBarrierGuard`. */ private class BarrierGuardGuardedNodeBridge extends Unit { abstract predicate guardedNode(Node n, Configuration config); @@ -370,3019 +247,94 @@ private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { } } -pragma[nomagic] -private predicate fullBarrier(NodeEx node, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n) +private FlowState relevantState(Configuration config) { + config.isSource(_, result) or + config.isSink(_, result) or + config.isBarrier(_, result) or + config.isAdditionalFlowStep(_, result, _, _) or + config.isAdditionalFlowStep(_, _, _, result) +} + +private newtype TConfigState = + TMkConfigState(Configuration config, FlowState state) { + state = relevantState(config) or state instanceof FlowStateEmpty + } + +private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) } + +private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) } + +private predicate singleConfiguration() { 1 = strictcount(Configuration c) } + +private module Config implements FullStateConfigSig { + class FlowState = TConfigState; + + predicate isSource(Node source, FlowState state) { + getConfig(state).isSource(source, getState(state)) or - config.isBarrierIn(n) and - not config.isSource(n) and - not config.isSource(n, _) + getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty + } + + predicate isSink(Node sink, FlowState state) { + getConfig(state).isSink(sink, getState(state)) or - config.isBarrierOut(n) and - not config.isSink(n) and - not config.isSink(n, _) + getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty + } + + predicate isBarrier(Node node) { none() } + + predicate isBarrier(Node node, FlowState state) { + getConfig(state).isBarrier(node, getState(state)) or + getConfig(state).isBarrier(node) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state)) + } + + predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) } + + predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) } + + predicate isAdditionalFlowStep(Node node1, Node node2) { + singleConfiguration() and + any(Configuration config).isAdditionalFlowStep(node1, node2) + } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and + getConfig(state2) = getConfig(state1) or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config) - ) -} - -pragma[nomagic] -private predicate stateBarrier(NodeEx node, FlowState state, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n, state) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config) - ) -} - -pragma[nomagic] -private predicate sourceNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSource(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSource(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -pragma[nomagic] -private predicate sinkNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSink(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSink(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -/** Provides the relevant barriers for a step from `node1` to `node2`. */ -pragma[inline] -private predicate stepFilter(NodeEx node1, NodeEx node2, Configuration config) { - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) -} - -/** - * Holds if data can flow in one local step from `node1` to `node2`. - */ -private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.asNode() = n and - node2.isImplicitReadNode(n, false) and - not fullBarrier(node1, config) - ) -} - -/** - * Holds if the additional step from `node1` to `node2` does not jump between callables. - */ -private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.isImplicitReadNode(n, true) and - node2.asNode() = n and - not fullBarrier(node2, config) - ) -} - -private predicate additionalLocalStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) - ) -} - -/** - * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. - */ -private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if the additional step from `node1` to `node2` jumps between callables. - */ -private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate additionalJumpStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -pragma[nomagic] -private predicate readSet(NodeEx node1, ContentSet c, NodeEx node2, Configuration config) { - readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and - stepFilter(node1, node2, config) - or - exists(Node n | - node2.isImplicitReadNode(n, true) and - node1.isImplicitReadNode(n, _) and - config.allowImplicitRead(n, c) - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(ContentSet cs | - readSet(node1, cs, node2, config) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate clearsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - clearsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate expectsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - expectsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -pragma[nomagic] -private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } - -pragma[nomagic] -private predicate hasReadStep(Content c, Configuration config) { read(_, c, _, config) } - -pragma[nomagic] -private predicate store( - NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config -) { - store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), - contentType) and - hasReadStep(tc.getContent(), config) and - stepFilter(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { - viableReturnPosOut(call, pos, out.asNode()) -} - -pragma[nomagic] -private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { - viableParamArg(call, p.asNode(), arg.asNode()) -} - -/** - * Holds if field flow should be used for the given configuration. - */ -private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } - -private predicate hasSourceCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSourceCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate hasSinkCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSinkCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if flow from `p` to a return node of kind `kind` is allowed. - * - * We don't expect a parameter to return stored in itself, unless - * explicitly allowed - */ -bindingset[p, kind] -private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { - exists(ParameterPosition pos | p.isParameterOf(_, pos) | - not kind.(ParamUpdateReturnKind).getPosition() = pos - or - allowParameterReturnInSelfCached(p.asNode()) - ) -} - -private module Stage1 implements StageSig { - class Ap extends int { - // workaround for bad functionality-induced joins (happens when using `Unit`) - pragma[nomagic] - Ap() { this in [0 .. 1] and this < 1 } - } - - private class Cc = boolean; - - /* Begin: Stage 1 logic. */ - /** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `cc` records whether the node is reached through an - * argument in a call. - */ - private predicate fwdFlow(NodeEx node, Cc cc, Configuration config) { - sourceNode(node, _, config) and - if hasSourceCallCtx(config) then cc = true else cc = false - or - exists(NodeEx mid | fwdFlow(mid, cc, config) | - localFlowStep(mid, node, config) or - additionalLocalFlowStep(mid, node, config) or - additionalLocalStateStep(mid, _, node, _, config) - ) - or - exists(NodeEx mid | fwdFlow(mid, _, config) and cc = false | - jumpStep(mid, node, config) or - additionalJumpStep(mid, node, config) or - additionalJumpStateStep(mid, _, node, _, config) - ) - or - // store - exists(NodeEx mid | - useFieldFlow(config) and - fwdFlow(mid, cc, config) and - store(mid, _, node, _, config) - ) - or - // read - exists(ContentSet c | - fwdFlowReadSet(c, node, cc, config) and - fwdFlowConsCandSet(c, _, config) - ) - or - // flow into a callable - fwdFlowIn(_, _, _, node, config) and - cc = true - or - // flow out of a callable - fwdFlowOut(_, node, false, config) and - cc = false - or - // flow through a callable - exists(DataFlowCall call | - fwdFlowOutFromArg(call, node, config) and - fwdFlowIsEntered(call, cc, config) - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowIn( - DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p, Configuration config - ) { - // call context cannot help reduce virtual dispatch - fwdFlow(arg, cc, config) and - viableParamArgEx(call, p, arg) and - not fullBarrier(p, config) and - ( - cc = false - or - cc = true and - not reducedViableImplInCallContext(call, _, _) - ) - or - // call context may help reduce virtual dispatch - exists(DataFlowCallable target | - fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target, config) and - target = viableImplInSomeFwdFlowCallContextExt(call, config) and - cc = true - ) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - fwdFlowIn(call, _, cc, _, config) - } - - pragma[nomagic] - private predicate fwdFlowInReducedViableImplInSomeCallContext( - DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target, Configuration config - ) { - fwdFlow(arg, true, config) and - viableParamArgEx(call, p, arg) and - reducedViableImplInCallContext(call, _, _) and - target = p.getEnclosingCallable() and - not fullBarrier(p, config) - } - - /** - * Gets a viable dispatch target of `call` in the context `ctx`. This is - * restricted to those `call`s for which a context might make a difference, - * and to `ctx`s that are reachable in `fwdFlow`. - */ - pragma[nomagic] - private DataFlowCallable viableImplInSomeFwdFlowCallContextExt( - DataFlowCall call, Configuration config - ) { - exists(DataFlowCall ctx | - fwdFlowIsEntered(ctx, _, config) and - result = viableImplInCallContextExt(call, ctx) - ) - } - - private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) } - - pragma[nomagic] - private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc, Configuration config) { - exists(NodeEx mid | - fwdFlow(mid, cc, config) and - readSet(mid, c, node, config) - ) - } - - /** - * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - fwdFlow(mid, _, config) and - store(mid, tc, node, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `cs` may be interpreted in a read as the target of some store - * into `c`, in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCandSet(ContentSet cs, Content c, Configuration config) { - fwdFlowConsCand(c, config) and - c = cs.getAReadContent() - } - - pragma[nomagic] - private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { - exists(RetNodeEx ret | - fwdFlow(ret, cc, config) and - ret.getReturnPosition() = pos - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) { - exists(ReturnPosition pos | - fwdFlowReturnPosition(pos, cc, config) and - viableReturnPosOutEx(call, pos, out) and - not fullBarrier(out, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) { - fwdFlowOut(call, out, true, config) - } - - private predicate stateStepFwd(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1 | - additionalLocalStateStep(node1, state1, _, state2, config) or - additionalJumpStateStep(node1, state1, _, state2, config) - | - fwdFlow(node1, config) - ) - } - - private predicate fwdFlowState(FlowState state, Configuration config) { - sourceNode(_, state, config) - or - exists(FlowState state0 | - fwdFlowState(state0, config) and - stateStepFwd(state0, state, config) - ) - } - - /** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink. - */ - pragma[nomagic] - private predicate revFlow(NodeEx node, boolean toReturn, Configuration config) { - revFlow0(node, toReturn, config) and - fwdFlow(node, config) - } - - pragma[nomagic] - private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) { - exists(FlowState state | - fwdFlow(node, pragma[only_bind_into](config)) and - sinkNode(node, state, config) and - fwdFlowState(state, pragma[only_bind_into](config)) and - if hasSinkCallCtx(config) then toReturn = true else toReturn = false - ) - or - exists(NodeEx mid | revFlow(mid, toReturn, config) | - localFlowStep(node, mid, config) or - additionalLocalFlowStep(node, mid, config) or - additionalLocalStateStep(node, _, mid, _, config) - ) - or - exists(NodeEx mid | revFlow(mid, _, config) and toReturn = false | - jumpStep(node, mid, config) or - additionalJumpStep(node, mid, config) or - additionalJumpStateStep(node, _, mid, _, config) - ) - or - // store - exists(Content c | - revFlowStore(c, node, toReturn, config) and - revFlowConsCand(c, config) - ) - or - // read - exists(NodeEx mid, ContentSet c | - readSet(node, c, mid, config) and - fwdFlowConsCandSet(c, _, pragma[only_bind_into](config)) and - revFlow(mid, toReturn, pragma[only_bind_into](config)) - ) - or - // flow into a callable - revFlowIn(_, node, false, config) and - toReturn = false - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(pos, config) and - node.(RetNodeEx).getReturnPosition() = pos and - toReturn = true - ) - or - // flow through a callable - exists(DataFlowCall call | - revFlowInToReturn(call, node, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - /** - * Holds if `c` is the target of a read in the flow covered by `revFlow`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, ContentSet cs | - fwdFlow(node, pragma[only_bind_into](config)) and - readSet(node, cs, mid, config) and - fwdFlowConsCandSet(cs, c, pragma[only_bind_into](config)) and - revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) { - exists(NodeEx mid, TypedContent tc | - revFlow(mid, toReturn, pragma[only_bind_into](config)) and - fwdFlowConsCand(c, pragma[only_bind_into](config)) and - store(node, tc, mid, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `c` is the target of both a read and a store in the flow covered - * by `revFlow`. - */ - pragma[nomagic] - additional predicate revFlowIsReadAndStored(Content c, Configuration conf) { - revFlowConsCand(c, conf) and - revFlowStore(c, _, _, conf) - } - - pragma[nomagic] - additional predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config - ) { - fwdFlowReturnPosition(pos, _, config) and - viableReturnPosOutEx(call, pos, out) - } - - pragma[nomagic] - private predicate revFlowOut(ReturnPosition pos, Configuration config) { - exists(NodeEx out | - revFlow(out, _, config) and - viableReturnPosOutNodeCandFwd1(_, pos, out, config) - ) - } - - pragma[nomagic] - additional predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config - ) { - fwdFlowIn(call, arg, _, p, config) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate revFlowIn( - DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config - ) { - exists(ParamNodeEx p | - revFlow(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) { - revFlowIn(call, arg, true, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(NodeEx out | - revFlow(out, toReturn, config) and - fwdFlowOutFromArg(call, out, config) - ) - } - - private predicate stateStepRev(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - additionalLocalStateStep(node1, state1, node2, state2, config) or - additionalJumpStateStep(node1, state1, node2, state2, config) - | - revFlow(node1, _, pragma[only_bind_into](config)) and - revFlow(node2, _, pragma[only_bind_into](config)) and - fwdFlowState(state1, pragma[only_bind_into](config)) and - fwdFlowState(state2, pragma[only_bind_into](config)) - ) - } - - additional predicate revFlowState(FlowState state, Configuration config) { - exists(NodeEx node | - sinkNode(node, state, config) and - revFlow(node, _, pragma[only_bind_into](config)) and - fwdFlowState(state, pragma[only_bind_into](config)) - ) - or - exists(FlowState state0 | - revFlowState(state0, config) and - stateStepRev(state, state0, config) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Content c | - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - revFlow(node2, pragma[only_bind_into](config)) and - store(node1, tc, node2, contentType, config) and - c = tc.getContent() and - exists(ap1) - ) - } - - pragma[nomagic] - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) { - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - read(n1, c, n2, pragma[only_bind_into](config)) and - revFlow(n2, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, config) and - exists(ap) - } - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, _, pragma[only_bind_into](config)) and - exists(state) and - exists(ap) - } - - private predicate throughFlowNodeCand(NodeEx node, Configuration config) { - revFlow(node, true, config) and - fwdFlow(node, true, config) and - not inBarrier(node, config) and - not outBarrier(node, config) - } - - /** Holds if flow may return from `callable`. */ - pragma[nomagic] - private predicate returnFlowCallableNodeCand( - DataFlowCallable callable, ReturnKindExt kind, Configuration config - ) { - exists(RetNodeEx ret | - throughFlowNodeCand(ret, config) and - callable = ret.getEnclosingCallable() and - kind = ret.getKind() - ) - } - - /** - * Holds if flow may enter through `p` and reach a return node making `p` a - * candidate for the origin of a summary. - */ - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(DataFlowCallable c, ReturnKindExt kind | - throughFlowNodeCand(p, config) and - returnFlowCallableNodeCand(c, kind, config) and - p.getEnclosingCallable() = c and - exists(ap) and - parameterFlowThroughAllowed(p, kind) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - throughFlowNodeCand(ret, config) and - kind = ret.getKind() and - exists(argAp) and - exists(ap) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, boolean toReturn | - revFlow(arg, toReturn, config) and - revFlowInToReturn(call, arg, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, config)) and - fields = count(Content f0 | fwdFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | fwdFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, config)) and - fields = count(Content f0 | revFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | revFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | revFlow(n, b, config)) - } - /* End: Stage 1 logic. */ -} - -pragma[noinline] -private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - localFlowStep(node1, node2, config) -} - -pragma[noinline] -private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - additionalLocalFlowStep(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCand1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config -) { - Stage1::revFlow(out, config) and - Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, Configuration config -) { - exists(ReturnPosition pos | - viableReturnPosOutNodeCand1(call, pos, out, config) and - pos = ret.getReturnPosition() and - kind = pos.getKind() and - Stage1::revFlow(ret, config) and - not outBarrier(ret, config) and - not inBarrier(out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCand1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config -) { - Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and - Stage1::revFlow(arg, config) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config -) { - viableParamArgNodeCand1(call, p, arg, config) and - Stage1::revFlow(p, config) and - not outBarrier(arg, config) and - not inBarrier(p, config) -} - -/** - * Gets the amount of forward branching on the origin of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int branch(NodeEx n1, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n1, _, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) - ) -} - -/** - * Gets the amount of backward branching on the target of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int join(NodeEx n2, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n, _, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) - ) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. The - * `allowsFieldFlow` flag indicates whether the branching is within the limit - * specified by the configuration. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, ret, kind, out, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(ret, pragma[only_bind_into](config)) and - j = join(out, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. The `allowsFieldFlow` flag indicates whether - * the branching is within the limit specified by the configuration. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config -) { - flowIntoCallNodeCand1(call, arg, p, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(arg, pragma[only_bind_into](config)) and - j = join(p, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -private signature module StageSig { - class Ap; - - predicate revFlow(NodeEx node, Configuration config); - - predicate revFlowAp(NodeEx node, Ap ap, Configuration config); - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config); - - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config); - - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config); - - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ); - - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ); - - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config); -} - -private module MkStage { - class ApApprox = PrevStage::Ap; - - signature module StageParam { - class Ap; - - class ApNil extends Ap; - - bindingset[result, ap] - ApApprox getApprox(Ap ap); - - ApNil getApNil(NodeEx node); - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail); - - /** - * An approximation of `Content` that corresponds to the precision level of - * `Ap`, such that the mappings from both `Ap` and `Content` to this type - * are functional. - */ - class ApHeadContent; - - ApHeadContent getHeadContent(Ap ap); - - ApHeadContent projectToHeadContent(Content c); - - class ApOption; - - ApOption apNone(); - - ApOption apSome(Ap ap); - - class Cc; - - class CcCall extends Cc; - - // TODO: member predicate on CcCall - predicate matchesCall(CcCall cc, DataFlowCall call); - - class CcNoCall extends Cc; - - Cc ccNone(); - - CcCall ccSomeCall(); - - class LocalCc; - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc); - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ); - - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config - ); - - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config - ); - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config); - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType); - } - - module Stage implements StageSig { - import Param - - /* Begin: Stage logic. */ - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - private predicate revFlowApAlias(NodeEx node, ApApprox apa, Configuration config) { - PrevStage::revFlowAp(node, apa, config) - } - - pragma[nomagic] - private predicate flowIntoCallApa( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa, - Configuration config - ) { - flowIntoCall(call, arg, p, allowsFieldFlow, config) and - PrevStage::revFlowAp(p, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(arg, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowOutOfCallApa( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - ApApprox apa, Configuration config - ) { - flowOutOfCall(call, ret, kind, out, allowsFieldFlow, config) and - PrevStage::revFlowAp(out, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(ret, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowThroughOutOfCall( - DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, - ApApprox argApa, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa, pragma[only_bind_into](config)) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) and - matchesCall(ccc, call) - ) - } - - /** - * Holds if `node` is reachable with access path `ap` from a source in the - * configuration `config`. - * - * The call context `cc` records whether the node is reached through an - * argument in a call, and if so, `summaryCtx` and `argAp` record the - * corresponding parameter position and access path of that argument, respectively. - */ - pragma[nomagic] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa, config) and - PrevStage::revFlow(node, state, apa, config) and - filter(node, state, ap, config) - } - - pragma[inline] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, cc, summaryCtx, argAp, ap, _, config) - } - - pragma[nomagic] - private predicate fwdFlow0( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - sourceNode(node, state, config) and - (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and - argAp = apNone() and - summaryCtx = TParamNodeNone() and - ap = getApNil(node) and - apa = getApprox(ap) - or - exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | - fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0, config) and - localCc = getLocalCc(mid, cc) - | - localStep(mid, state0, node, state, true, _, config, localCc) and - ap = ap0 and - apa = apa0 - or - localStep(mid, state0, node, state, false, ap, config, localCc) and - ap0 instanceof ApNil and - apa = getApprox(ap) - ) - or - exists(NodeEx mid | - fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa, pragma[only_bind_into](config)) and - jumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(mid, state, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(mid, state0, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStateStep(mid, state0, node, state, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - // store - exists(TypedContent tc, Ap ap0 | - fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp, config) and - ap = apCons(tc, ap0) and - apa = getApprox(ap) - ) - or - // read - exists(Ap ap0, Content c | - fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp, config) and - fwdFlowConsCand(ap0, c, ap, config) and - apa = getApprox(ap) - ) - or - // flow into a callable - fwdFlowIn(_, node, state, _, cc, _, _, ap, apa, config) and - if PrevStage::parameterMayFlowThrough(node, apa, config) - then ( - summaryCtx = TParamNodeSome(node.asNode()) and - argAp = apSome(ap) - ) else ( - summaryCtx = TParamNodeNone() and argAp = apNone() - ) - or - // flow out of a callable - exists( - DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, - DataFlowCallable inner - | - fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa, config) and - flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa, config) and - inner = ret.getEnclosingCallable() and - cc = getCallContextReturn(inner, call, innercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - or - // flow through a callable - exists( - DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, ApApprox innerArgApa - | - fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowStore( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(DataFlowType contentType, ApApprox apa1 | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1, config) and - PrevStage::storeStepCand(node1, apa1, tc, node2, contentType, config) and - typecheckStore(ap1, contentType) - ) - } - - /** - * Holds if forward flow with access path `tail` reaches a store of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(TypedContent tc | - fwdFlowStore(_, tail, tc, _, _, _, _, _, config) and - tc.getContent() = c and - cons = apCons(tc, tail) - ) - } - - pragma[nomagic] - private predicate readStepCand( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - PrevStage::readStepCand(node1, c, node2, config) and - apc = projectToHeadContent(c) - } - - bindingset[node1, apc] - pragma[inline_late] - private predicate readStepCand0( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - readStepCand(node1, apc, c, node2, config) - } - - pragma[nomagic] - private predicate fwdFlowRead( - Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(ApHeadContent apc | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap, config) and - apc = getHeadContent(ap) and - readStepCand0(node1, apc, c, node2, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowIn( - DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, - ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa, Configuration config - ) { - exists(ArgNodeEx arg, boolean allowsFieldFlow | - fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa, config) and - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowRetFromArg( - RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, ApApprox argApa, - Ap ap, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - fwdFlow(pragma[only_bind_into](ret), state, ccc, - TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), - pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa), - pragma[only_bind_into](config)) and - kind = ret.getKind() and - parameterFlowThroughAllowed(summaryCtx, kind) and - argApa = getApprox(argAp) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) - ) - } - - pragma[inline] - private predicate fwdFlowThrough0( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, - Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa, config) and - fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp, config) - } - - pragma[nomagic] - private predicate fwdFlowThrough( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa, - config) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` - * and data might flow through the target callable and back out at `call`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered( - DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, - ParamNodeEx p, Ap ap, Configuration config - ) { - exists(ApApprox apa | - fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(p, apa, config) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate storeStepFwd( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config - ) { - fwdFlowStore(node1, ap1, tc, node2, _, _, _, _, config) and - ap2 = apCons(tc, ap1) and - fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _, config) - } - - private predicate readStepFwd( - NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config - ) { - fwdFlowRead(ap1, c, n1, n2, _, _, _, _, config) and - fwdFlowConsCand(ap1, c, ap2, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough0( - DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, - ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, - innerArgApa, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough( - RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, - Ap ap, Configuration config - ) { - exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | - returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa, config) and - pos = ret.getReturnPosition() and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowThroughIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap, - Configuration config - ) { - exists(ApApprox argApa | - flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), - allowsFieldFlow, argApa, pragma[only_bind_into](config)) and - fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa, - pragma[only_bind_into](config)) and - returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap, - pragma[only_bind_into](config)) and - if allowsFieldFlow = false then argAp instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowIntoCallAp( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap, - Configuration config - ) { - exists(ApApprox apa | - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - fwdFlow(arg, _, _, _, _, ap, apa, config) - ) - } - - pragma[nomagic] - private predicate flowOutOfCallAp( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, - Ap ap, Configuration config - ) { - exists(ApApprox apa | - flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa, config) and - fwdFlow(ret, _, _, _, _, ap, apa, config) and - pos = ret.getReturnPosition() - ) - } - - /** - * Holds if `node` with access path `ap` is part of a path from a source to a - * sink in the configuration `config`. - * - * The parameter `returnCtx` records whether (and how) the node must be returned - * from the enclosing callable in order to reach a sink, and if so, `returnAp` - * records the access path of the returned value. - */ - pragma[nomagic] - additional predicate revFlow( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - revFlow0(node, state, returnCtx, returnAp, ap, config) and - fwdFlow(node, state, _, _, _, ap, config) - } - - pragma[nomagic] - private predicate revFlow0( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, _, _, _, ap, config) and - sinkNode(node, state, config) and - ( - if hasSinkCallCtx(config) - then returnCtx = TReturnCtxNoFlowThrough() - else returnCtx = TReturnCtxNone() - ) and - returnAp = apNone() and - ap instanceof ApNil - or - exists(NodeEx mid, FlowState state0 | - localStep(node, state, mid, state0, true, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, ap, config) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap, pragma[only_bind_into](config)) and - localStep(node, pragma[only_bind_into](state), mid, state0, false, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, nil, pragma[only_bind_into](config)) and - ap instanceof ApNil - ) - or - exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, state, _, _, ap, config) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStep(node, mid, config) and - revFlow(pragma[only_bind_into](mid), state, _, _, nil, pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStateStep(node, state, mid, state0, config) and - revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil, - pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - // store - exists(Ap ap0, Content c | - revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp, config) and - revFlowConsCand(ap0, c, ap, config) - ) - or - // read - exists(NodeEx mid, Ap ap0 | - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - readStepFwd(node, ap, _, mid, ap0, config) - ) - or - // flow into a callable - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, state, TReturnCtxNone(), returnAp, ap, config) and - flowIntoCallAp(_, node, p, allowsFieldFlow, ap, config) and - (if allowsFieldFlow = false then ap instanceof ApNil else any()) and - returnCtx = TReturnCtxNone() - ) - or - // flow through a callable - exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, node, p, _, ap, innerReturnAp, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(_, node, pos, state, _, _, ap, config) and - if returnFlowsThrough(node, pos, state, _, _, _, ap, config) - then ( - returnCtx = TReturnCtxMaybeFlowThrough(pos) and - returnAp = apSome(ap) - ) else ( - returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() - ) - ) - } - - pragma[nomagic] - private predicate revFlowStore( - Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, - ReturnCtx returnCtx, ApOption returnAp, Configuration config - ) { - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - storeStepFwd(node, ap, tc, mid, ap0, config) and - tc.getContent() = c - } - - /** - * Holds if reverse flow with access path `tail` reaches a read of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(NodeEx mid, Ap tail0 | - revFlow(mid, _, _, _, tail, config) and - tail = pragma[only_bind_into](tail0) and - readStepFwd(_, cons, c, mid, tail0, config) - ) - } - - pragma[nomagic] - private predicate revFlowOut( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, - ApOption returnAp, Ap ap, Configuration config - ) { - exists(NodeEx out, boolean allowsFieldFlow | - revFlow(out, state, returnCtx, returnAp, ap, config) and - flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate revFlowParamToReturn( - ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap, Configuration config - ) { - revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), - pragma[only_bind_into](ap), pragma[only_bind_into](config)) and - parameterFlowThroughAllowed(p, pos.getKind()) and - PrevStage::parameterMayFlowThrough(p, getApprox(ap), config) - } - - pragma[nomagic] - private predicate revFlowThrough( - DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, - ApOption returnAp, Ap ap, Ap innerReturnAp, Configuration config - ) { - revFlowParamToReturn(p, state, pos, innerReturnAp, ap, config) and - revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned( - DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap, - Configuration config - ) { - exists(RetNodeEx ret, FlowState state, CcCall ccc | - revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap, config) and - returnFlowsThrough(ret, pos, state, ccc, _, _, ap, config) and - matchesCall(ccc, call) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Ap ap2, Content c | - PrevStage::storeStepCand(node1, _, tc, node2, contentType, config) and - revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _, config) and - revFlowConsCand(ap2, c, ap1, config) - ) - } - - predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(Ap ap1, Ap ap2 | - revFlow(node2, _, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and - readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _, - pragma[only_bind_into](config)) - ) - } - - additional predicate revFlow(NodeEx node, FlowState state, Configuration config) { - revFlow(node, state, _, _, _, config) - } - - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, _, _, ap, config) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, _, _, _, ap, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, Configuration config) { - revFlow(node, _, _, _, _, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, ap, config) - } - - private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepFwd(_, ap, tc, _, _, config) - } - - private predicate revConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepCand(_, ap, tc, _, _, config) - } - - private predicate validAp(Ap ap, Configuration config) { - revFlow(_, _, _, _, ap, config) and ap instanceof ApNil - or - exists(TypedContent head, Ap tail | - consCand(head, tail, config) and - ap = apCons(head, tail) - ) - } - - additional predicate consCand(TypedContent tc, Ap ap, Configuration config) { - revConsCand(tc, ap, config) and - validAp(ap, config) - } - - pragma[nomagic] - private predicate parameterFlowsThroughRev( - ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp, Configuration config - ) { - revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap, config) and - parameterFlowThroughAllowed(p, pos.getKind()) - } - - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(ReturnPosition pos | - returnFlowsThrough(_, pos, _, _, p, ap, _, config) and - parameterFlowsThroughRev(p, ap, pos, _, config) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - exists(ParamNodeEx p, ReturnPosition pos | - returnFlowsThrough(ret, pos, _, _, p, argAp, ap, config) and - parameterFlowsThroughRev(p, argAp, pos, ap, config) and - kind = pos.getKind() - ) - } - - pragma[nomagic] - private predicate revFlowThroughArg( - DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, - Ap ap, Configuration config - ) { - exists(ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp, config) - ) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | - revFlow(arg, state, returnCtx, returnAp, ap, config) and - revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, config)) and - fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and - states = count(FlowState state | fwdFlow(_, state, _, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | - fwdFlow(n, state, cc, summaryCtx, argAp, ap, config) - ) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, _, _, _, config)) and - fields = count(TypedContent f0 | consCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and - states = count(FlowState state | revFlow(_, state, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | - revFlow(n, state, returnCtx, retAp, ap, config) - ) - } - /* End: Stage logic. */ - } -} - -private module BooleanCallContext { - class Cc extends boolean { - Cc() { this in [true, false] } - } - - class CcCall extends Cc { - CcCall() { this = true } - } - - /** Holds if the call context may be `call`. */ - predicate matchesCall(CcCall cc, DataFlowCall call) { any() } - - class CcNoCall extends Cc { - CcNoCall() { this = false } - } - - Cc ccNone() { result = false } - - CcCall ccSomeCall() { result = true } - - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } -} - -private module Level1CallContext { - class Cc = CallContext; - - class CcCall = CallContextCall; - - pragma[inline] - predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } - - class CcNoCall = CallContextNoCall; - - Cc ccNone() { result instanceof CallContextAny } - - CcCall ccSomeCall() { result instanceof CallContextSomeCall } - - module NoLocalCallContext { - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSiteDispatch(call, c) - then result = TSpecificCall(call) - else result = TSomeCall() - } - } - - module LocalCallContext { - class LocalCc = LocalCallContext; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { - result = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - node.getEnclosingCallable()) - } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() - } - } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { - checkCallContextReturn(innercc, c, call) and - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() - } -} - -private module Stage2Param implements MkStage::StageParam { - private module PrevStage = Stage1; - - class Ap extends boolean { - Ap() { this in [true, false] } - } - - class ApNil extends Ap { - ApNil() { this = false } - } - - bindingset[result, ap] - PrevStage::Ap getApprox(Ap ap) { any() } - - ApNil getApNil(NodeEx node) { Stage1::revFlow(node, _) and exists(result) } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } - - class ApHeadContent = Unit; - - pragma[inline] - ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } - - ApHeadContent projectToHeadContent(Content c) { any() } - - class ApOption = BooleanOption; - - ApOption apNone() { result = TBooleanNone() } - - ApOption apSome(Ap ap) { result = TBooleanSome(ap) } - - import Level1CallContext - import NoLocalCallContext - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - ( - preservesValue = true and - localFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalStateStep(node1, state1, node2, state2, config) - ) and - exists(ap) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand1/6; - - predicate flowIntoCall = flowIntoCallNodeCand1/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - expectsContentEx(node, c) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - PrevStage::revFlowState(state, pragma[only_bind_into](config)) and - exists(ap) and - not stateBarrier(node, state, config) and - ( - notExpectsContent(node) - or - ap = true and - expectsContentCand(node, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage2 implements StageSig { - import MkStage::Stage -} - -pragma[nomagic] -private predicate flowOutOfCallNodeCand2( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -pragma[nomagic] -private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -private module LocalFlowBigStep { - /** - * A node where some checking is required, and hence the big-step relation - * is not allowed to step over. - */ - private class FlowCheckNode extends NodeEx { - FlowCheckNode() { - castNode(this.asNode()) or - clearsContentCached(this.asNode(), _) or - expectsContentCached(this.asNode(), _) - } - } - - /** - * Holds if `node` can be the first node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowEntry(NodeEx node, FlowState state, Configuration config) { - Stage2::revFlow(node, state, config) and - ( - sourceNode(node, state, config) - or - jumpStep(_, node, config) - or - additionalJumpStep(_, node, config) - or - additionalJumpStateStep(_, _, node, state, config) - or - node instanceof ParamNodeEx - or - node.asNode() instanceof OutNodeExt - or - Stage2::storeStepCand(_, _, _, node, _, config) - or - Stage2::readStepCand(_, _, node, config) - or - node instanceof FlowCheckNode - or - exists(FlowState s | - additionalLocalStateStep(_, s, node, state, config) and - s != state - ) - ) - } - - /** - * Holds if `node` can be the last node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowExit(NodeEx node, FlowState state, Configuration config) { - exists(NodeEx next | Stage2::revFlow(next, state, config) | - jumpStep(node, next, config) or - additionalJumpStep(node, next, config) or - flowIntoCallNodeCand2(_, node, next, _, config) or - flowOutOfCallNodeCand2(_, node, _, next, _, config) or - Stage2::storeStepCand(node, _, _, next, _, config) or - Stage2::readStepCand(node, _, next, config) - ) - or - exists(NodeEx next, FlowState s | Stage2::revFlow(next, s, config) | - additionalJumpStateStep(node, state, next, s, config) - or - additionalLocalStateStep(node, state, next, s, config) and - s != state - ) - or - Stage2::revFlow(node, state, config) and - node instanceof FlowCheckNode - or - sinkNode(node, state, config) - } - - pragma[noinline] - private predicate additionalLocalFlowStepNodeCand2( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, Configuration config - ) { - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 and - Stage2::revFlow(node1, pragma[only_bind_into](state1), false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false, - pragma[only_bind_into](config)) - or - additionalLocalStateStep(node1, state1, node2, state2, config) and - Stage2::revFlow(node1, state1, false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, state2, false, pragma[only_bind_into](config)) - } - - /** - * Holds if the local path from `node1` to `node2` is a prefix of a maximal - * subsequence of local flow steps in a dataflow path. - * - * This is the transitive closure of `[additional]localFlowStep` beginning - * at `localFlowEntry`. - */ - pragma[nomagic] - private predicate localFlowStepPlus( - NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, - Configuration config, LocalCallContext cc - ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and - ( - localFlowEntry(node1, pragma[only_bind_into](state), pragma[only_bind_into](config)) and - ( - localFlowStepNodeCand1(node1, node2, config) and - preservesValue = true and - t = node1.getDataFlowType() and // irrelevant dummy value - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - or - additionalLocalFlowStepNodeCand2(node1, state, node2, state, config) and - preservesValue = false and - t = node2.getDataFlowType() - ) and - node1 != node2 and - cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) - or - exists(NodeEx mid | - localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, - pragma[only_bind_into](config), cc) and - localFlowStepNodeCand1(mid, node2, config) and - not mid instanceof FlowCheckNode and - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - ) - or - exists(NodeEx mid | - localFlowStepPlus(node1, state, mid, _, _, pragma[only_bind_into](config), cc) and - additionalLocalFlowStepNodeCand2(mid, state, node2, state, config) and - not mid instanceof FlowCheckNode and - preservesValue = false and - t = node2.getDataFlowType() - ) - ) - } - - /** - * Holds if `node1` can step to `node2` in one or more local steps and this - * path can occur as a maximal subsequence of local steps in a dataflow path. - */ - pragma[nomagic] - predicate localFlowBigStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - DataFlowType t, Configuration config, LocalCallContext callContext - ) { - localFlowStepPlus(node1, state1, node2, preservesValue, t, config, callContext) and - localFlowExit(node2, state1, config) and - state1 = state2 - or - additionalLocalFlowStepNodeCand2(node1, state1, node2, state2, config) and - state1 != state2 and - preservesValue = false and - t = node2.getDataFlowType() and - callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) - } -} - -private import LocalFlowBigStep - -private module Stage3Param implements MkStage::StageParam { - private module PrevStage = Stage2; - - class Ap = ApproxAccessPathFront; - - class ApNil = ApproxAccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } - - class ApHeadContent = ContentApprox; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - predicate projectToHeadContent = getContentApprox/1; - - class ApOption = ApproxAccessPathFrontOption; - - ApOption apNone() { result = TApproxAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } - - import BooleanCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApproxAccessPathFrontNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand2/6; - - predicate flowIntoCall = flowIntoCallNodeCand2/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getAHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage3 implements StageSig { - import MkStage::Stage -} - -private module Stage4Param implements MkStage::StageParam { - private module PrevStage = Stage3; - - class Ap = AccessPathFront; - - class ApNil = AccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathFrontOption; - - ApOption apNone() { result = TAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } - - import BooleanCallContext - - pragma[nomagic] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) and - exists(lcc) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clearSet(NodeEx node, ContentSet c, Configuration config) { - PrevStage::revFlow(node, config) and - clearsContentCached(node.asNode(), c) - } - - pragma[nomagic] - private predicate clearContent(NodeEx node, Content c, Configuration config) { - exists(ContentSet cs | - PrevStage::readStepCand(_, pragma[only_bind_into](c), _, pragma[only_bind_into](config)) and - c = cs.getAReadContent() and - clearSet(node, cs, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clear(NodeEx node, Ap ap, Configuration config) { - clearContent(node, ap.getHead().getContent(), config) - } - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - not clear(node, ap, config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage4 implements StageSig { - import MkStage::Stage -} - -/** - * Holds if `argApf` is recorded as the summary context for flow reaching `node` - * and remains relevant for the following pruning stage. - */ -private predicate flowCandSummaryCtx( - NodeEx node, FlowState state, AccessPathFront argApf, Configuration config -) { - exists(AccessPathFront apf | - Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf, config) and - Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf, - config) - ) -} - -/** - * Holds if a length 2 access path approximation with the head `tc` is expected - * to be expensive. - */ -private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { - exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf, config)) and - nodes = - strictcount(NodeEx n, FlowState state | - Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - or - flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - ) and - accessPathApproxCostLimits(apLimit, tupleLimit) and - apLimit < tails and - tupleLimit < (tails - 1) * nodes and - not tc.forceHighPrecision() - ) -} - -private newtype TAccessPathApprox = - TNil(DataFlowType t) or - TConsNil(TypedContent tc, DataFlowType t) { - Stage4::consCand(tc, TFrontNil(t), _) and - not expensiveLen2unfolding(tc, _) - } or - TConsCons(TypedContent tc1, TypedContent tc2, int len) { - Stage4::consCand(tc1, TFrontHead(tc2), _) and - len in [2 .. accessPathLimit()] and - not expensiveLen2unfolding(tc1, _) - } or - TCons1(TypedContent tc, int len) { - len in [1 .. accessPathLimit()] and - expensiveLen2unfolding(tc, _) - } - -/** - * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only - * the first two elements of the list and its length are tracked. If data flows - * from a source to a given node with a given `AccessPathApprox`, this indicates - * the sequence of dereference operations needed to get from the value in the node - * to the tracked object. The final type indicates the type of the tracked object. - */ -abstract private class AccessPathApprox extends TAccessPathApprox { - abstract string toString(); - - abstract TypedContent getHead(); - - abstract int len(); - - abstract DataFlowType getType(); - - abstract AccessPathFront getFront(); - - /** Gets the access path obtained by popping `head` from this path, if any. */ - abstract AccessPathApprox pop(TypedContent head); -} - -private class AccessPathApproxNil extends AccessPathApprox, TNil { - private DataFlowType t; - - AccessPathApproxNil() { this = TNil(t) } - - override string toString() { result = concat(": " + ppReprType(t)) } - - override TypedContent getHead() { none() } - - override int len() { result = 0 } - - override DataFlowType getType() { result = t } - - override AccessPathFront getFront() { result = TFrontNil(t) } - - override AccessPathApprox pop(TypedContent head) { none() } -} - -abstract private class AccessPathApproxCons extends AccessPathApprox { } - -private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { - private TypedContent tc; - private DataFlowType t; - - AccessPathApproxConsNil() { this = TConsNil(tc, t) } - - override string toString() { - // The `concat` becomes "" if `ppReprType` has no result. - result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) - } - - override TypedContent getHead() { result = tc } - - override int len() { result = 1 } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } -} - -private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { - private TypedContent tc1; - private TypedContent tc2; - private int len; - - AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - - override string toString() { - if len = 2 - then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" - else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc1 } - - override int len() { result = len } - - override DataFlowType getType() { result = tc1.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc1) } - - override AccessPathApprox pop(TypedContent head) { - head = tc1 and - ( - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - } -} - -private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { - private TypedContent tc; - private int len; - - AccessPathApproxCons1() { this = TCons1(tc, len) } - - override string toString() { - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc } - - override int len() { result = len } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { - head = tc and - ( - exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2), _) | - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - or - exists(DataFlowType t | - len = 1 and - Stage4::consCand(tc, TFrontNil(t), _) and - result = TNil(t) - ) - ) - } -} - -/** Gets the access path obtained by popping `tc` from `ap`, if any. */ -private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } - -/** Gets the access path obtained by pushing `tc` onto `ap`. */ -private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } - -private newtype TAccessPathApproxOption = - TAccessPathApproxNone() or - TAccessPathApproxSome(AccessPathApprox apa) - -private class AccessPathApproxOption extends TAccessPathApproxOption { - string toString() { - this = TAccessPathApproxNone() and result = "" - or - this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) - } -} - -private module Stage5Param implements MkStage::StageParam { - private module PrevStage = Stage4; - - class Ap = AccessPathApprox; - - class ApNil = AccessPathApproxNil; - - pragma[nomagic] - PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathApproxOption; - - ApOption apNone() { result = TAccessPathApproxNone() } - - ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } - - import Level1CallContext - import LocalCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, lcc) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { any() } - - // Type checking is not necessary here as it has already been done in stage 3. - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage5 = MkStage::Stage; - -bindingset[conf, result] -private Configuration unbindConf(Configuration conf) { - exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c)) -} - -pragma[nomagic] -private predicate nodeMayUseSummary0( - NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(AccessPathApprox apa0 | - Stage5::parameterMayFlowThrough(p, _, _) and - Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0, config) and - Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), - TAccessPathApproxSome(apa), apa0, config) - ) -} - -pragma[nomagic] -private predicate nodeMayUseSummary( - NodeEx n, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::parameterMayFlowThrough(p, apa, config) and - nodeMayUseSummary0(n, p, state, apa, config) - ) -} - -private newtype TSummaryCtx = - TSummaryCtxNone() or - TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { - exists(Configuration config | - Stage5::parameterMayFlowThrough(p, ap.getApprox(), config) and - Stage5::revFlow(p, state, _, config) - ) - } - -/** - * A context for generating flow summaries. This represents flow entry through - * a specific parameter with an access path of a specific shape. - * - * Summaries are only created for parameters that may flow through. - */ -abstract private class SummaryCtx extends TSummaryCtx { - abstract string toString(); -} - -/** A summary context from which no flow summary can be generated. */ -private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { - override string toString() { result = "" } -} - -/** A summary context from which a flow summary can be generated. */ -private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParamNodeEx p; - private FlowState s; - private AccessPath ap; - - SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } - - ParameterPosition getParameterPos() { p.isParameterOf(_, result) } - - ParamNodeEx getParamNode() { result = p } - - override string toString() { result = p + ": " + ap } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** - * Gets the number of length 2 access path approximations that correspond to `apa`. - */ -private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { - exists(TypedContent tc, int len | - tc = apa.getHead() and - len = apa.len() and - result = - strictcount(AccessPathFront apf | - Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), - config) - ) - ) -} - -private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = - strictcount(NodeEx n, FlowState state | - Stage5::revFlow(n, state, apa, config) or nodeMayUseSummary(n, state, apa, config) - ) -} - -/** - * Holds if a length 2 access path approximation matching `apa` is expected - * to be expensive. - */ -private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = count1to2unfold(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - apLimit < aps and - tupleLimit < (aps - 1) * nodes - ) -} - -private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { - exists(TypedContent head | - apa.pop(head) = result and - Stage5::consCand(head, result, config) - ) -} - -/** - * Holds with `unfold = false` if a precise head-tail representation of `apa` is - * expected to be expensive. Holds with `unfold = true` otherwise. - */ -private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { - if apa.getHead().forceHighPrecision() - then unfold = true - else - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = countPotentialAps(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true - ) -} - -/** - * Gets the number of `AccessPath`s that correspond to `apa`. - */ -pragma[assume_small_delta] -private int countAps(AccessPathApprox apa, Configuration config) { - evalUnfold(apa, false, config) and - result = 1 and - (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) - or - evalUnfold(apa, false, config) and - result = count1to2unfold(apa, config) and - not expensiveLen1to2unfolding(apa, config) - or - evalUnfold(apa, true, config) and - result = countPotentialAps(apa, config) -} - -/** - * Gets the number of `AccessPath`s that would correspond to `apa` assuming - * that it is expanded to a precise head-tail representation. - */ -language[monotonicAggregates] -pragma[assume_small_delta] -private int countPotentialAps(AccessPathApprox apa, Configuration config) { - apa instanceof AccessPathApproxNil and result = 1 - or - result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) -} - -private newtype TAccessPath = - TAccessPathNil(DataFlowType t) or - TAccessPathCons(TypedContent head, AccessPath tail) { - exists(AccessPathApproxCons apa | - not evalUnfold(apa, false, _) and - head = apa.getHead() and - tail.getApprox() = getATail(apa, _) - ) - } or - TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - not expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head1 = apa.getHead() and - head2 = getATail(apa, _).getHead() - ) - } or - TAccessPathCons1(TypedContent head, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head = apa.getHead() - ) - } - -private newtype TPathNode = - pragma[assume_small_delta] - TPathNodeMid( - NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config - ) { - // A PathNode is introduced by a source ... - Stage5::revFlow(node, state, config) and - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - or - // ... or a step from an existing PathNode to another node. - exists(PathNodeMid mid | - pathStep(mid, node, state, cc, sc, ap) and - pragma[only_bind_into](config) = mid.getConfiguration() and - Stage5::revFlow(node, state, ap.getApprox(), pragma[only_bind_into](config)) - ) - } or - TPathNodeSink(NodeEx node, FlowState state, Configuration config) { - exists(PathNodeMid sink | - sink.isAtSink() and - node = sink.getNodeEx() and - state = sink.getState() and - config = sink.getConfiguration() - ) - } or - TPathNodeSourceGroup(string sourceGroup, Configuration config) { - exists(PathNodeImpl source | - sourceGroup = source.getSourceGroup() and - config = source.getConfiguration() - ) - } or - TPathNodeSinkGroup(string sinkGroup, Configuration config) { - exists(PathNodeSink sink | - sinkGroup = sink.getSinkGroup() and - config = sink.getConfiguration() - ) - } - -/** - * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a - * source to a given node with a given `AccessPath`, this indicates the sequence - * of dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ -private class AccessPath extends TAccessPath { - /** Gets the head of this access path, if any. */ - abstract TypedContent getHead(); - - /** Gets the tail of this access path, if any. */ - abstract AccessPath getTail(); - - /** Gets the front of this access path. */ - abstract AccessPathFront getFront(); - - /** Gets the approximation of this access path. */ - abstract AccessPathApprox getApprox(); - - /** Gets the length of this access path. */ - abstract int length(); - - /** Gets a textual representation of this access path. */ - abstract string toString(); - - /** Gets the access path obtained by popping `tc` from this access path, if any. */ - final AccessPath pop(TypedContent tc) { - result = this.getTail() and - tc = this.getHead() - } - - /** Gets the access path obtained by pushing `tc` onto this access path. */ - final AccessPath push(TypedContent tc) { this = result.pop(tc) } -} - -private class AccessPathNil extends AccessPath, TAccessPathNil { - private DataFlowType t; - - AccessPathNil() { this = TAccessPathNil(t) } - - DataFlowType getType() { result = t } - - override TypedContent getHead() { none() } - - override AccessPath getTail() { none() } - - override AccessPathFrontNil getFront() { result = TFrontNil(t) } - - override AccessPathApproxNil getApprox() { result = TNil(t) } - - override int length() { result = 0 } - - override string toString() { result = concat(": " + ppReprType(t)) } -} - -private class AccessPathCons extends AccessPath, TAccessPathCons { - private TypedContent head; - private AccessPath tail; - - AccessPathCons() { this = TAccessPathCons(head, tail) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { result = tail } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - pragma[assume_small_delta] - override AccessPathApproxCons getApprox() { - result = TConsNil(head, tail.(AccessPathNil).getType()) - or - result = TConsCons(head, tail.getHead(), this.length()) - or - result = TCons1(head, this.length()) - } - - pragma[assume_small_delta] - override int length() { result = 1 + tail.length() } - - private string toStringImpl(boolean needsSuffix) { - exists(DataFlowType t | - tail = TAccessPathNil(t) and - needsSuffix = false and - result = head.toString() + "]" + concat(" : " + ppReprType(t)) - ) - or - result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) - or - exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | - result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true - or - result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false - ) - or - exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | - result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true - or - result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false - ) - } - - override string toString() { - result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" - or - result = "[" + this.toStringImpl(false) - } -} - -private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { - private TypedContent head1; - private TypedContent head2; - private int len; - - AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } - - override TypedContent getHead() { result = head1 } - - override AccessPath getTail() { - Stage5::consCand(head1, result.getApprox(), _) and - result.getHead() = head2 and - result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head1) } - - override AccessPathApproxCons getApprox() { - result = TConsCons(head1, head2, len) or - result = TCons1(head1, len) - } - - override int length() { result = len } - - override string toString() { - if len = 2 - then result = "[" + head1.toString() + ", " + head2.toString() + "]" - else - result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" - } -} - -private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { - private TypedContent head; - private int len; - - AccessPathCons1() { this = TAccessPathCons1(head, len) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { - Stage5::consCand(head, result.getApprox(), _) and result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - - override int length() { result = len } - - override string toString() { - if len = 1 - then result = "[" + head.toString() + "]" - else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" - } -} - -abstract private class PathNodeImpl extends TPathNode { - /** Gets the `FlowState` of this node. */ - abstract FlowState getState(); - - /** Gets the associated configuration. */ - abstract Configuration getConfiguration(); - - /** Holds if this node is a source. */ - abstract predicate isSource(); - - abstract PathNodeImpl getASuccessorImpl(); - - private PathNodeImpl getASuccessorIfHidden() { - this.isHidden() and - result = this.getASuccessorImpl() - } - - pragma[nomagic] - private PathNodeImpl getANonHiddenSuccessor0() { - result = this.getASuccessorIfHidden*() and - not result.isHidden() - } - - final PathNodeImpl getANonHiddenSuccessor() { - result = this.getASuccessorImpl().getANonHiddenSuccessor0() and - not this.isHidden() - } - - abstract NodeEx getNodeEx(); - - predicate isHidden() { - not this.getConfiguration().includeHiddenNodes() and - ( - hiddenNode(this.getNodeEx().asNode()) and - not this.isSource() and - not this instanceof PathNodeSink - or - this.getNodeEx() instanceof TNodeImplicitRead - ) - } - - string getSourceGroup() { - this.isSource() and - this.getConfiguration().sourceGrouping(this.getNodeEx().asNode(), result) + not singleConfiguration() and + getConfig(state1).isAdditionalFlowStep(node1, node2) and + state2 = state1 } - predicate isFlowSource() { - this.isSource() and not exists(this.getSourceGroup()) - or - this instanceof PathNodeSourceGroup + predicate allowImplicitRead(Node node, ContentSet c) { + any(Configuration config).allowImplicitRead(node, c) } - predicate isFlowSink() { - this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or - this instanceof PathNodeSinkGroup - } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } - private string ppAp() { - this instanceof PathNodeSink and result = "" - or - exists(string s | s = this.(PathNodeMid).getAp().toString() | - if s = "" then result = "" else result = " " + s - ) - } + FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } - private string ppCtx() { - this instanceof PathNodeSink and result = "" - or - result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + predicate sourceGrouping(Node source, string sourceGroup) { + any(Configuration config).sourceGrouping(source, sourceGroup) } - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + predicate sinkGrouping(Node sink, string sinkGroup) { + any(Configuration config).sinkGrouping(sink, sinkGroup) } -} - -/** Holds if `n` can reach a sink. */ -private predicate directReach(PathNodeImpl n) { - n instanceof PathNodeSink or - n instanceof PathNodeSinkGroup or - directReach(n.getANonHiddenSuccessor()) -} - -/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ -private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } -/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { - n1.getANonHiddenSuccessor() = n2 and directReach(n2) + predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() } } -private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) +private import Impl as I +import I /** * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. */ -class PathNode instanceof PathNodeImpl { - PathNode() { reach(this) } - +class PathNode instanceof I::PathNode { /** Gets a textual representation of this element. */ final string toString() { result = super.toString() } @@ -3406,1548 +358,39 @@ class PathNode instanceof PathNodeImpl { } /** Gets the underlying `Node`. */ - final Node getNode() { super.getNodeEx().projectToNode() = result } + final Node getNode() { result = super.getNode() } /** Gets the `FlowState` of this node. */ - final FlowState getState() { result = super.getState() } + final FlowState getState() { result = getState(super.getState()) } /** Gets the associated configuration. */ - final Configuration getConfiguration() { result = super.getConfiguration() } + final Configuration getConfiguration() { result = getConfig(super.getState()) } /** Gets a successor of this node, if any. */ - final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } + final PathNode getASuccessor() { result = super.getASuccessor() } /** Holds if this node is a source. */ final predicate isSource() { super.isSource() } /** Holds if this node is a grouping of source nodes. */ - final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group, _) } + final predicate isSourceGroup(string group) { super.isSourceGroup(group) } /** Holds if this node is a grouping of sink nodes. */ - final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group, _) } + final predicate isSinkGroup(string group) { super.isSinkGroup(group) } } -/** - * Provides the query predicates needed to include a graph in a path-problem query. - */ -module PathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } - - /** Holds if `n` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode n, string key, string val) { - key = "semmle.label" and val = n.toString() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { - Subpaths::subpaths(arg, par, ret, out) - } -} - -/** - * An intermediate flow graph node. This is a triple consisting of a `Node`, - * a `CallContext`, and a `Configuration`. - */ -private class PathNodeMid extends PathNodeImpl, TPathNodeMid { - NodeEx node; - FlowState state; - CallContext cc; - SummaryCtx sc; - AccessPath ap; - Configuration config; - - PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - SummaryCtx getSummaryCtx() { result = sc } - - AccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - private PathNodeMid getSuccMid() { - pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx(), result.getAp()) and - result.getConfiguration() = unbindConf(this.getConfiguration()) - } - - override PathNodeImpl getASuccessorImpl() { - // an intermediate step to another intermediate node - result = this.getSuccMid() - or - // a final step to a sink - result = this.getSuccMid().projectToSink() - } - - override predicate isSource() { - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - } - - predicate isAtSink() { - sinkNode(node, state, config) and - ap instanceof AccessPathNil and - if hasSinkCallCtx(config) - then - // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` - // is exactly what we need to check. This also implies - // `sc instanceof SummaryCtxNone`. - // For `FeatureEqualSourceSinkCallContext` the initial call context was - // set to `CallContextSomeCall` and jumps are disallowed, so - // `cc instanceof CallContextNoCall` never holds. On the other hand, - // in this case there's never any need to enter a call except to identify - // a summary, so the condition in `pathIntoCallable` enforces this, which - // means that `sc instanceof SummaryCtxNone` holds if and only if we are - // in the call context of the source. - sc instanceof SummaryCtxNone or - cc instanceof CallContextNoCall - else any() - } - - PathNodeSink projectToSink() { - this.isAtSink() and - result.getNodeEx() = node and - result.getState() = state and - result.getConfiguration() = unbindConf(config) - } -} - -/** - * A flow graph node corresponding to a sink. This is disjoint from the - * intermediate nodes in order to uniquely correspond to a given sink by - * excluding the `CallContext`. - */ -private class PathNodeSink extends PathNodeImpl, TPathNodeSink { - NodeEx node; - FlowState state; - Configuration config; - - PathNodeSink() { this = TPathNodeSink(node, state, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result = TPathNodeSinkGroup(this.getSinkGroup(), config) - } - - override predicate isSource() { sourceNode(node, state, config) } - - string getSinkGroup() { config.sinkGrouping(node.asNode(), result) } -} - -private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { - string sourceGroup; - Configuration config; - - PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result.getSourceGroup() = sourceGroup and - result.getConfiguration() = config - } - - override predicate isSource() { none() } - - override string toString() { result = sourceGroup } - - override 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 - } -} - -private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { - string sinkGroup; - Configuration config; - - PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { none() } - - override predicate isSource() { none() } - - override string toString() { result = sinkGroup } - - override 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 - } -} - -private predicate pathNode( - PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, - Configuration conf, LocalCallContext localCC -) { - midnode = mid.getNodeEx() and - state = mid.getState() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - midnode.getEnclosingCallable()) and - ap = mid.getAp() -} - -/** - * Holds if data may flow from `mid` to `node`. The last step in or out of - * a callable is recorded by `cc`. - */ -pragma[assume_small_delta] -pragma[nomagic] -private predicate pathStep( - PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap -) { - exists(NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC | - pathNode(mid, midnode, state0, cc, sc, ap, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, true, _, conf, localCC) - ) - or - exists( - AccessPath ap0, NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC - | - pathNode(mid, midnode, state0, cc, sc, ap0, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), conf, - localCC) and - ap0 instanceof AccessPathNil - ) - or - jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - ap = mid.getAp() - or - additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, mid.getConfiguration()) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - pathIntoCallable(mid, node, state, _, cc, sc, _, _) and ap = mid.getAp() - or - pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone - or - pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() -} - -pragma[nomagic] -private predicate pathReadStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - tc = ap0.getHead() and - Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -pragma[nomagic] -private predicate pathStoreStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -private predicate pathOutOfCallable0( - PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa, - Configuration config -) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - apa = mid.getAp().getApprox() and - config = mid.getConfiguration() -} - -pragma[nomagic] -private predicate pathOutOfCallable1( - PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPathApprox apa, Configuration config -) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - pathOutOfCallable0(mid, pos, state, innercc, apa, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() +private predicate hasFlow(Node source, Node sink, Configuration config) { + exists(PathNode source0, PathNode sink0 | + hasFlowPath(source0, sink0, config) and + source0.getNode() = source and + sink0.getNode() = sink ) } -pragma[noinline] -private NodeEx getAnOutNodeFlow( - ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config -) { - result.asNode() = kind.getAnOutNode(call) and - Stage5::revFlow(result, _, apa, config) +private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) { + hasFlowPath(source, sink) and source.getConfiguration() = config } -/** - * Holds if data may flow from `mid` to `out`. The last step of this path - * is a return from a callable and is recorded by `cc`, if needed. - */ -pragma[noinline] -private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { - exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | - pathOutOfCallable1(mid, call, kind, state, cc, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} +private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) } -/** - * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. - */ -pragma[noinline] -private predicate pathIntoArg( - PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(ArgNodeEx arg, ArgumentPosition apos | - pathNode(mid, arg, state, cc, _, ap, config, _) and - arg.asNode().(ArgNode).argumentOf(call, apos) and - apa = ap.getApprox() and - parameterMatch(ppos, apos) - ) -} - -pragma[nomagic] -private predicate parameterCand( - DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::revFlow(p, _, apa, config) and - p.isParameterOf(callable, pos) - ) -} - -pragma[nomagic] -private predicate pathIntoCallable0( - PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, AccessPath ap, Configuration config -) { - exists(AccessPathApprox apa | - pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - callable = resolveCall(call, outercc) and - parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa), - pragma[only_bind_into](config)) - ) -} - -/** - * Holds if data may flow from `mid` to `p` through `call`. The contexts - * before and after entering the callable are `outercc` and `innercc`, - * respectively. - */ -pragma[nomagic] -private predicate pathIntoCallable( - PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, - SummaryCtx sc, DataFlowCall call, Configuration config -) { - exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | - pathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - ( - sc = TSummaryCtxSome(p, state, ap) - or - not exists(TSummaryCtxSome(p, state, ap)) and - sc = TSummaryCtxNone() and - // When the call contexts of source and sink needs to match then there's - // never any reason to enter a callable except to find a summary. See also - // the comment in `PathNodeMid::isAtSink`. - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) -} - -/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ -pragma[nomagic] -private predicate paramFlowsThrough( - ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, - AccessPathApprox apa, Configuration config -) { - exists(RetNodeEx ret | - pathNode(_, ret, state, cc, sc, ap, config, _) and - kind = ret.getKind() and - apa = ap.getApprox() and - parameterFlowThroughAllowed(sc.getParamNode(), kind) - ) -} - -pragma[nomagic] -private predicate pathThroughCallable0( - DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(CallContext innercc, SummaryCtx sc | - pathIntoCallable(mid, _, _, cc, innercc, sc, call, config) and - paramFlowsThrough(kind, state, innercc, sc, ap, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` through a callable to the node `out`. - * The context `cc` is restored to its value prior to entering the callable. - */ -pragma[noinline] -private predicate pathThroughCallable( - PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap -) { - exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config | - pathThroughCallable0(call, mid, kind, state, cc, ap, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -private module Subpaths { - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths01( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - exists(Configuration config | - pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and - pathIntoCallable(arg, par, _, _, innercc, sc, _, config) and - paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, - pragma[only_bind_into](apout), _, unbindConf(config)) and - not arg.isHidden() - ) - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `sout`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths02( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and - out.asNode() = kind.getAnOutNode(_) - } - - pragma[nomagic] - private Configuration getPathNodeConf(PathNodeImpl n) { result = n.getConfiguration() } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple. - */ - pragma[nomagic] - private predicate subpaths03( - PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, AccessPath apout - ) { - exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | - subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and - pathNode(ret, retnode, sout, innercc, sc, apout, unbindConf(getPathNodeConf(arg)), _) and - kind = retnode.getKind() - ) - } - - private PathNodeImpl localStepToHidden(PathNodeImpl n) { - n.getASuccessorImpl() = result and - result.isHidden() and - exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | - localFlowBigStep(n1, _, n2, _, _, _, _, _) or - store(n1, _, n2, _, _) or - readSet(n1, _, n2, _) - ) - } - - pragma[nomagic] - private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { - succ = pred.getANonHiddenSuccessor() and - succNode = succ.getNodeEx() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { - exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | - pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and - subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and - hasSuccessor(pragma[only_bind_into](arg), par, p) and - not ret.isHidden() and - pathNode(out0, o, sout, _, _, apout, _, _) - | - out = out0 or out = out0.projectToSink() - ) - } - - /** - * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. - */ - predicate retReach(PathNodeImpl n) { - exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) - or - exists(PathNodeImpl mid | - retReach(mid) and - n.getANonHiddenSuccessor() = mid and - not subpaths(_, mid, _, _) - ) - } -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -private predicate hasFlowPath( - PathNodeImpl flowsource, PathNodeImpl flowsink, Configuration configuration -) { - flowsource.isFlowSource() and - flowsource.getConfiguration() = configuration and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.isFlowSink() -} - -private predicate flowsTo( - PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink, - Configuration configuration -) { - flowsource.isSource() and - flowsource.getConfiguration() = configuration and - flowsource.getNodeEx().asNode() = source and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.getNodeEx().asNode() = sink -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -predicate flowsTo(Node source, Node sink, Configuration configuration) { - flowsTo(_, _, source, sink, configuration) -} - -private predicate finalStats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples -) { - fwd = true and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and - tuples = count(PathNodeImpl pn) - or - fwd = false and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and - tuples = count(PathNode pn) -} - -/** - * INTERNAL: Only for debugging. - * - * Calculates per-stage metrics for data flow. - */ -predicate stageStats( - int n, string stage, int nodes, int fields, int conscand, int states, int tuples, - Configuration config -) { - stage = "1 Fwd" and - n = 10 and - Stage1::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "1 Rev" and - n = 15 and - Stage1::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Fwd" and - n = 20 and - Stage2::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Rev" and - n = 25 and - Stage2::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Fwd" and - n = 30 and - Stage3::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Rev" and - n = 35 and - Stage3::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Fwd" and - n = 40 and - Stage4::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Rev" and - n = 45 and - Stage4::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Fwd" and - n = 50 and - Stage5::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Rev" and - n = 55 and - Stage5::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) - or - stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) -} - -private module FlowExploration { - private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - jumpStep(node1, node2, config) - or - additionalJumpStep(node1, node2, config) - or - additionalJumpStateStep(node1, _, node2, _, config) - or - // flow into callable - viableParamArgEx(_, node2, node1) - or - // flow out of a callable - viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) - | - c1 = node1.getEnclosingCallable() and - c2 = node2.getEnclosingCallable() and - c1 != c2 - ) - } - - private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSource(n) or config.isSource(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSrc(mid, config) and callableStep(mid, c, config) - ) - } - - private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSink(n) or config.isSink(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSink(mid, config) and callableStep(c, mid, config) - ) - } - - private newtype TCallableExt = - TCallable(DataFlowCallable c, Configuration config) { - interestingCallableSrc(c, config) or - interestingCallableSink(c, config) - } or - TCallableSrc() or - TCallableSink() - - private predicate callableExtSrc(TCallableSrc src) { any() } - - private predicate callableExtSink(TCallableSink sink) { any() } - - private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { - exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | - callableStep(c1, c2, config) and - ce1 = TCallable(c1, pragma[only_bind_into](config)) and - ce2 = TCallable(c2, pragma[only_bind_into](config)) - ) - or - exists(Node n, Configuration config | - ce1 = TCallableSrc() and - (config.isSource(n) or config.isSource(n, _)) and - ce2 = TCallable(getNodeEnclosingCallable(n), config) - ) - or - exists(Node n, Configuration config | - ce2 = TCallableSink() and - (config.isSink(n) or config.isSink(n, _)) and - ce1 = TCallable(getNodeEnclosingCallable(n), config) - ) - } - - private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { - callableExtStepFwd(ce2, ce1) - } - - private int distSrcExt(TCallableExt c) = - shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) - - private int distSinkExt(TCallableExt c) = - shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) - - private int distSrc(DataFlowCallable c, Configuration config) { - result = distSrcExt(TCallable(c, config)) - 1 - } - - private int distSink(DataFlowCallable c, Configuration config) { - result = distSinkExt(TCallable(c, config)) - 1 - } - - private newtype TPartialAccessPath = - TPartialNil(DataFlowType t) or - TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first - * element of the list and its length are tracked. If data flows from a source to - * a given node with a given `AccessPath`, this indicates the sequence of - * dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ - private class PartialAccessPath extends TPartialAccessPath { - abstract string toString(); - - TypedContent getHead() { this = TPartialCons(result, _) } - - int len() { - this = TPartialNil(_) and result = 0 - or - this = TPartialCons(_, result) - } - - DataFlowType getType() { - this = TPartialNil(result) - or - exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) - } - } - - private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { - override string toString() { - exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) - } - } - - private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { - override string toString() { - exists(TypedContent tc, int len | this = TPartialCons(tc, len) | - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private newtype TRevPartialAccessPath = - TRevPartialNil() or - TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `Content`s, but only the first - * element of the list and its length are tracked. - */ - private class RevPartialAccessPath extends TRevPartialAccessPath { - abstract string toString(); - - Content getHead() { this = TRevPartialCons(result, _) } - - int len() { - this = TRevPartialNil() and result = 0 - or - this = TRevPartialCons(_, result) - } - } - - private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { - override string toString() { result = "" } - } - - private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { - override string toString() { - exists(Content c, int len | this = TRevPartialCons(c, len) | - if len = 1 - then result = "[" + c.toString() + "]" - else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private predicate relevantState(FlowState state) { - sourceNode(_, state, _) or - sinkNode(_, state, _) or - additionalLocalStateStep(_, state, _, _, _) or - additionalLocalStateStep(_, _, _, state, _) or - additionalJumpStateStep(_, state, _, _, _) or - additionalJumpStateStep(_, _, _, state, _) - } - - private newtype TSummaryCtx1 = - TSummaryCtx1None() or - TSummaryCtx1Param(ParamNodeEx p) - - private newtype TSummaryCtx2 = - TSummaryCtx2None() or - TSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TSummaryCtx3 = - TSummaryCtx3None() or - TSummaryCtx3Some(PartialAccessPath ap) - - private newtype TRevSummaryCtx1 = - TRevSummaryCtx1None() or - TRevSummaryCtx1Some(ReturnPosition pos) - - private newtype TRevSummaryCtx2 = - TRevSummaryCtx2None() or - TRevSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TRevSummaryCtx3 = - TRevSummaryCtx3None() or - TRevSummaryCtx3Some(RevPartialAccessPath ap) - - private newtype TPartialPathNode = - TPartialPathNodeFwd( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = TPartialNil(node.getDataFlowType()) and - exists(config.explorationLimit()) - or - partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap, config) and - distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() - } or - TPartialPathNodeRev( - NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, - RevPartialAccessPath ap, Configuration config - ) { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() and - exists(config.explorationLimit()) - or - revPartialPathStep(_, node, state, sc1, sc2, sc3, ap, config) and - not clearsContentEx(node, ap.getHead()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead()) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() - } - - pragma[nomagic] - private predicate partialPathNodeMk0( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap, config) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - not clearsContentEx(node, ap.getHead().getContent()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead().getContent()) - ) and - if node.asNode() instanceof CastingNode - then compatibleTypes(node.getDataFlowType(), ap.getType()) - else any() - } - - /** - * A `Node` augmented with a call context, an access path, and a configuration. - */ - class PartialPathNode extends TPartialPathNode { - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() - } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - /** Gets the underlying `Node`. */ - final Node getNode() { this.getNodeEx().projectToNode() = result } - - FlowState getState() { none() } - - private NodeEx getNodeEx() { - result = this.(PartialPathNodeFwd).getNodeEx() or - result = this.(PartialPathNodeRev).getNodeEx() - } - - /** Gets the associated configuration. */ - Configuration getConfiguration() { none() } - - /** Gets a successor of this node, if any. */ - PartialPathNode getASuccessor() { none() } - - /** - * Gets the approximate distance to the nearest source measured in number - * of interprocedural steps. - */ - int getSourceDistance() { - result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - /** - * Gets the approximate distance to the nearest sink measured in number - * of interprocedural steps. - */ - int getSinkDistance() { - result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - private string ppAp() { - exists(string s | - s = this.(PartialPathNodeFwd).getAp().toString() or - s = this.(PartialPathNodeRev).getAp().toString() - | - if s = "" then result = "" else result = " " + s - ) - } - - private string ppCtx() { - result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" - } - - /** Holds if this is a source in a forward-flow path. */ - predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } - - /** Holds if this is a sink in a reverse-flow path. */ - predicate isRevSink() { this.(PartialPathNodeRev).isSink() } - } - - /** - * Provides the query predicates needed to include a graph in a path-problem query. - */ - module PartialPathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } - } - - private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { - NodeEx node; - FlowState state; - CallContext cc; - TSummaryCtx1 sc1; - TSummaryCtx2 sc2; - TSummaryCtx3 sc3; - PartialAccessPath ap; - Configuration config; - - PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - TSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TSummaryCtx3 getSummaryCtx3() { result = sc3 } - - PartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeFwd getASuccessor() { - partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp(), - result.getConfiguration()) - } - - predicate isSource() { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap instanceof TPartialNil - } - } - - private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { - NodeEx node; - FlowState state; - TRevSummaryCtx1 sc1; - TRevSummaryCtx2 sc2; - TRevSummaryCtx3 sc3; - RevPartialAccessPath ap; - Configuration config; - - PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } - - RevPartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeRev getASuccessor() { - revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), - this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp(), this.getConfiguration()) - } - - predicate isSink() { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() - } - } - - private predicate partialPathStep( - PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, - TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and - ( - localFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - ) - or - jumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - partialPathStoreStep(mid, _, _, node, ap) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(PartialAccessPath ap0, TypedContent tc | - partialPathReadStep(mid, ap0, tc, node, cc, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsFwd(ap, tc, ap0, config) - ) - or - partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap, config) - or - partialPathOutOfCallable(mid, node, state, cc, ap, config) and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() - or - partialPathThroughCallable(mid, node, state, cc, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - bindingset[result, i] - private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } - - pragma[inline] - private predicate partialPathStoreStep( - PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, - PartialAccessPath ap2 - ) { - exists(NodeEx midNode, DataFlowType contentType | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - store(midNode, tc, node, contentType, mid.getConfiguration()) and - ap2.getHead() = tc and - ap2.len() = unbindInt(ap1.len() + 1) and - compatibleTypes(ap1.getType(), contentType) - ) - } - - pragma[nomagic] - private predicate apConsFwd( - PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeFwd mid | - partialPathStoreStep(mid, ap1, tc, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate partialPathReadStep( - PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc, - Configuration config - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and - ap.getHead() = tc and - pragma[only_bind_into](config) = mid.getConfiguration() and - cc = mid.getCallContext() - ) - } - - private predicate partialPathOutOfCallable0( - PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, - PartialAccessPath ap, Configuration config - ) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - ap = mid.getAp() and - config = mid.getConfiguration() - } - - pragma[nomagic] - private predicate partialPathOutOfCallable1( - PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - partialPathOutOfCallable0(mid, pos, state, innercc, ap, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) - } - - private predicate partialPathOutOfCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(ReturnKindExt kind, DataFlowCall call | - partialPathOutOfCallable1(mid, call, kind, state, cc, ap, config) - | - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[noinline] - private predicate partialPathIntoArg( - PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ArgNode arg, ArgumentPosition apos | - arg = mid.getNodeEx().asNode() and - state = mid.getState() and - cc = mid.getCallContext() and - arg.argumentOf(call, apos) and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate partialPathIntoCallable0( - PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - partialPathIntoArg(mid, pos, state, outercc, call, ap, config) and - callable = resolveCall(call, outercc) - } - - private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, - CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ParameterPosition pos, DataFlowCallable callable | - partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - sc1 = TSummaryCtx1Param(p) and - sc2 = TSummaryCtx2Some(state) and - sc3 = TSummaryCtx3Some(ap) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) - } - - pragma[nomagic] - private predicate paramFlowsThroughInPartialPath( - ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeFwd mid, RetNodeEx ret | - mid.getNodeEx() = ret and - kind = ret.getKind() and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() and - ap = mid.getAp() - ) - } - - pragma[noinline] - private predicate partialPathThroughCallable0( - DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | - partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _, config) and - paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap, config) - ) - } - - private predicate partialPathThroughCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ReturnKindExt kind | - partialPathThroughCallable0(call, mid, kind, state, cc, ap, config) and - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[nomagic] - private predicate revPartialPathStep( - PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, - TRevSummaryCtx3 sc3, RevPartialAccessPath ap, Configuration config - ) { - localFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - jumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - revPartialPathReadStep(mid, _, _, node, ap) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(RevPartialAccessPath ap0, Content c | - revPartialPathStoreStep(mid, ap0, c, node, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsRev(ap, c, ap0, config) - ) - or - exists(ParamNodeEx p | - mid.getNodeEx() = p and - viableParamArgEx(_, p, node) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - or - exists(ReturnPosition pos | - revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap, config) and - pos = getReturnPosition(node.asNode()) - ) - or - revPartialPathThroughCallable(mid, node, state, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - pragma[inline] - private predicate revPartialPathReadStep( - PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, - RevPartialAccessPath ap2 - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - read(node, c, midNode, mid.getConfiguration()) and - ap2.getHead() = c and - ap2.len() = unbindInt(ap1.len() + 1) - ) - } - - pragma[nomagic] - private predicate apConsRev( - RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeRev mid | - revPartialPathReadStep(mid, ap1, c, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathStoreStep( - PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config - ) { - exists(NodeEx midNode, TypedContent tc | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - store(node, tc, midNode, _, config) and - ap.getHead() = c and - config = mid.getConfiguration() and - tc.getContent() = c - ) - } - - pragma[nomagic] - private predicate revPartialPathIntoReturn( - PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, - TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap, - Configuration config - ) { - exists(NodeEx out | - mid.getNodeEx() = out and - mid.getState() = state and - viableReturnPosOutEx(call, pos, out) and - sc1 = TRevSummaryCtx1Some(pos) and - sc2 = TRevSummaryCtx2Some(state) and - sc3 = TRevSummaryCtx3Some(ap) and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathFlowsThrough( - ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, - TRevSummaryCtx3Some sc3, RevPartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | - mid.getNodeEx() = p and - mid.getState() = state and - p.getPosition() = ppos and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable0( - DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, - RevPartialAccessPath ap, Configuration config - ) { - exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | - revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _, config) and - revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap, config) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ArgumentPosition pos | - revPartialPathThroughCallable0(call, mid, pos, state, ap, config) and - node.asNode().(ArgNode).argumentOf(call, pos) - ) - } -} - -import FlowExploration - -private predicate partialFlow( - PartialPathNode source, PartialPathNode node, Configuration configuration -) { - source.getConfiguration() = configuration and - source.isFwdSource() and - node = source.getASuccessor+() -} - -private predicate revPartialFlow( - PartialPathNode node, PartialPathNode sink, Configuration configuration -) { - sink.getConfiguration() = configuration and - sink.isRevSink() and - node.getASuccessor+() = sink -} +predicate flowsTo = hasFlow/3; diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll index 1b969756b09..e6bdc74cceb 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll @@ -1,16 +1,16 @@ /** - * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from - * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed - * through the `Configuration` class. This file exists in several identical - * copies, allowing queries to use multiple `Configuration` classes that depend - * on each other without introducing mutual recursion among those configurations. + * DEPRECATED: Use `Make` and `MakeWithState` instead. + * + * Provides a `Configuration` class backwards-compatible interface to the data + * flow library. */ private import DataFlowImplCommon private import DataFlowImplSpecific::Private import DataFlowImplSpecific::Public +private import DataFlowImpl import DataFlowImplCommonPublic +import FlowStateString /** * A configuration of interprocedural data flow analysis. This defines @@ -144,6 +144,10 @@ abstract class Configuration extends string { * - `FeatureEqualSourceSinkCallContext`: * Implies both of the above and additionally ensures that the entire flow * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. */ FlowFeature getAFeature() { none() } @@ -156,7 +160,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from `source` to `sink` for this configuration. */ - predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } + predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) } /** * Holds if data may flow from `source` to `sink` for this configuration. @@ -169,9 +173,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from some source to `sink` for this configuration. */ - predicate hasFlowTo(Node sink) { - sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode() - } + predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) } /** * Holds if data may flow from some source to `sink` for this configuration. @@ -179,10 +181,12 @@ abstract class Configuration extends string { predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } /** + * DEPRECATED: Use `FlowExploration` instead. + * * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` * measured in approximate number of interprocedural steps. */ - int explorationLimit() { none() } + deprecated int explorationLimit() { none() } /** * Holds if hidden nodes should be included in the data flow graph. @@ -191,49 +195,6 @@ abstract class Configuration extends string { * is not visualized (for example in a `path-problem` query). */ predicate includeHiddenNodes() { none() } - - /** - * Holds if there is a partial data flow path from `source` to `node`. The - * approximate distance between `node` and the closest source is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards sink definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sources is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - */ - final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { - partialFlow(source, node, this) and - dist = node.getSourceDistance() - } - - /** - * Holds if there is a partial data flow path from `node` to `sink`. The - * approximate distance between `node` and the closest sink is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards source definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sinks is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - * - * Note that reverse flow has slightly lower precision than the corresponding - * forward flow, as reverse flow disregards type pruning among other features. - */ - final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { - revPartialFlow(node, sink, this) and - dist = node.getSinkDistance() - } } /** @@ -263,90 +224,6 @@ abstract private class ConfigurationRecursionPrevention extends Configuration { } } -private newtype TNodeEx = - TNodeNormal(Node n) or - TNodeImplicitRead(Node n, boolean hasRead) { - any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true] - } - -private class NodeEx extends TNodeEx { - string toString() { - result = this.asNode().toString() - or - exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") - } - - Node asNode() { this = TNodeNormal(result) } - - predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } - - Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } - - pragma[nomagic] - private DataFlowCallable getEnclosingCallable0() { - nodeEnclosingCallable(this.projectToNode(), result) - } - - pragma[inline] - DataFlowCallable getEnclosingCallable() { - pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) - } - - pragma[nomagic] - private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } - - pragma[inline] - DataFlowType getDataFlowType() { - pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) - } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -private class ArgNodeEx extends NodeEx { - ArgNodeEx() { this.asNode() instanceof ArgNode } -} - -private class ParamNodeEx extends NodeEx { - ParamNodeEx() { this.asNode() instanceof ParamNode } - - predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { - this.asNode().(ParamNode).isParameterOf(c, pos) - } - - ParameterPosition getPosition() { this.isParameterOf(_, result) } -} - -private class RetNodeEx extends NodeEx { - RetNodeEx() { this.asNode() instanceof ReturnNodeExt } - - ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } - - ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } -} - -private predicate inBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierIn(n) - | - config.isSource(n) or config.isSource(n, _) - ) -} - -private predicate outBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierOut(n) - | - config.isSink(n) or config.isSink(n, _) - ) -} - /** A bridge class to access the deprecated `isBarrierGuard`. */ private class BarrierGuardGuardedNodeBridge extends Unit { abstract predicate guardedNode(Node n, Configuration config); @@ -370,3019 +247,94 @@ private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { } } -pragma[nomagic] -private predicate fullBarrier(NodeEx node, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n) +private FlowState relevantState(Configuration config) { + config.isSource(_, result) or + config.isSink(_, result) or + config.isBarrier(_, result) or + config.isAdditionalFlowStep(_, result, _, _) or + config.isAdditionalFlowStep(_, _, _, result) +} + +private newtype TConfigState = + TMkConfigState(Configuration config, FlowState state) { + state = relevantState(config) or state instanceof FlowStateEmpty + } + +private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) } + +private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) } + +private predicate singleConfiguration() { 1 = strictcount(Configuration c) } + +private module Config implements FullStateConfigSig { + class FlowState = TConfigState; + + predicate isSource(Node source, FlowState state) { + getConfig(state).isSource(source, getState(state)) or - config.isBarrierIn(n) and - not config.isSource(n) and - not config.isSource(n, _) + getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty + } + + predicate isSink(Node sink, FlowState state) { + getConfig(state).isSink(sink, getState(state)) or - config.isBarrierOut(n) and - not config.isSink(n) and - not config.isSink(n, _) + getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty + } + + predicate isBarrier(Node node) { none() } + + predicate isBarrier(Node node, FlowState state) { + getConfig(state).isBarrier(node, getState(state)) or + getConfig(state).isBarrier(node) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state)) + } + + predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) } + + predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) } + + predicate isAdditionalFlowStep(Node node1, Node node2) { + singleConfiguration() and + any(Configuration config).isAdditionalFlowStep(node1, node2) + } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and + getConfig(state2) = getConfig(state1) or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config) - ) -} - -pragma[nomagic] -private predicate stateBarrier(NodeEx node, FlowState state, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n, state) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config) - ) -} - -pragma[nomagic] -private predicate sourceNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSource(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSource(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -pragma[nomagic] -private predicate sinkNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSink(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSink(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -/** Provides the relevant barriers for a step from `node1` to `node2`. */ -pragma[inline] -private predicate stepFilter(NodeEx node1, NodeEx node2, Configuration config) { - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) -} - -/** - * Holds if data can flow in one local step from `node1` to `node2`. - */ -private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.asNode() = n and - node2.isImplicitReadNode(n, false) and - not fullBarrier(node1, config) - ) -} - -/** - * Holds if the additional step from `node1` to `node2` does not jump between callables. - */ -private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.isImplicitReadNode(n, true) and - node2.asNode() = n and - not fullBarrier(node2, config) - ) -} - -private predicate additionalLocalStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) - ) -} - -/** - * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. - */ -private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if the additional step from `node1` to `node2` jumps between callables. - */ -private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate additionalJumpStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -pragma[nomagic] -private predicate readSet(NodeEx node1, ContentSet c, NodeEx node2, Configuration config) { - readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and - stepFilter(node1, node2, config) - or - exists(Node n | - node2.isImplicitReadNode(n, true) and - node1.isImplicitReadNode(n, _) and - config.allowImplicitRead(n, c) - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(ContentSet cs | - readSet(node1, cs, node2, config) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate clearsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - clearsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate expectsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - expectsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -pragma[nomagic] -private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } - -pragma[nomagic] -private predicate hasReadStep(Content c, Configuration config) { read(_, c, _, config) } - -pragma[nomagic] -private predicate store( - NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config -) { - store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), - contentType) and - hasReadStep(tc.getContent(), config) and - stepFilter(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { - viableReturnPosOut(call, pos, out.asNode()) -} - -pragma[nomagic] -private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { - viableParamArg(call, p.asNode(), arg.asNode()) -} - -/** - * Holds if field flow should be used for the given configuration. - */ -private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } - -private predicate hasSourceCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSourceCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate hasSinkCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSinkCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if flow from `p` to a return node of kind `kind` is allowed. - * - * We don't expect a parameter to return stored in itself, unless - * explicitly allowed - */ -bindingset[p, kind] -private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { - exists(ParameterPosition pos | p.isParameterOf(_, pos) | - not kind.(ParamUpdateReturnKind).getPosition() = pos - or - allowParameterReturnInSelfCached(p.asNode()) - ) -} - -private module Stage1 implements StageSig { - class Ap extends int { - // workaround for bad functionality-induced joins (happens when using `Unit`) - pragma[nomagic] - Ap() { this in [0 .. 1] and this < 1 } - } - - private class Cc = boolean; - - /* Begin: Stage 1 logic. */ - /** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `cc` records whether the node is reached through an - * argument in a call. - */ - private predicate fwdFlow(NodeEx node, Cc cc, Configuration config) { - sourceNode(node, _, config) and - if hasSourceCallCtx(config) then cc = true else cc = false - or - exists(NodeEx mid | fwdFlow(mid, cc, config) | - localFlowStep(mid, node, config) or - additionalLocalFlowStep(mid, node, config) or - additionalLocalStateStep(mid, _, node, _, config) - ) - or - exists(NodeEx mid | fwdFlow(mid, _, config) and cc = false | - jumpStep(mid, node, config) or - additionalJumpStep(mid, node, config) or - additionalJumpStateStep(mid, _, node, _, config) - ) - or - // store - exists(NodeEx mid | - useFieldFlow(config) and - fwdFlow(mid, cc, config) and - store(mid, _, node, _, config) - ) - or - // read - exists(ContentSet c | - fwdFlowReadSet(c, node, cc, config) and - fwdFlowConsCandSet(c, _, config) - ) - or - // flow into a callable - fwdFlowIn(_, _, _, node, config) and - cc = true - or - // flow out of a callable - fwdFlowOut(_, node, false, config) and - cc = false - or - // flow through a callable - exists(DataFlowCall call | - fwdFlowOutFromArg(call, node, config) and - fwdFlowIsEntered(call, cc, config) - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowIn( - DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p, Configuration config - ) { - // call context cannot help reduce virtual dispatch - fwdFlow(arg, cc, config) and - viableParamArgEx(call, p, arg) and - not fullBarrier(p, config) and - ( - cc = false - or - cc = true and - not reducedViableImplInCallContext(call, _, _) - ) - or - // call context may help reduce virtual dispatch - exists(DataFlowCallable target | - fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target, config) and - target = viableImplInSomeFwdFlowCallContextExt(call, config) and - cc = true - ) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - fwdFlowIn(call, _, cc, _, config) - } - - pragma[nomagic] - private predicate fwdFlowInReducedViableImplInSomeCallContext( - DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target, Configuration config - ) { - fwdFlow(arg, true, config) and - viableParamArgEx(call, p, arg) and - reducedViableImplInCallContext(call, _, _) and - target = p.getEnclosingCallable() and - not fullBarrier(p, config) - } - - /** - * Gets a viable dispatch target of `call` in the context `ctx`. This is - * restricted to those `call`s for which a context might make a difference, - * and to `ctx`s that are reachable in `fwdFlow`. - */ - pragma[nomagic] - private DataFlowCallable viableImplInSomeFwdFlowCallContextExt( - DataFlowCall call, Configuration config - ) { - exists(DataFlowCall ctx | - fwdFlowIsEntered(ctx, _, config) and - result = viableImplInCallContextExt(call, ctx) - ) - } - - private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) } - - pragma[nomagic] - private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc, Configuration config) { - exists(NodeEx mid | - fwdFlow(mid, cc, config) and - readSet(mid, c, node, config) - ) - } - - /** - * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - fwdFlow(mid, _, config) and - store(mid, tc, node, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `cs` may be interpreted in a read as the target of some store - * into `c`, in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCandSet(ContentSet cs, Content c, Configuration config) { - fwdFlowConsCand(c, config) and - c = cs.getAReadContent() - } - - pragma[nomagic] - private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { - exists(RetNodeEx ret | - fwdFlow(ret, cc, config) and - ret.getReturnPosition() = pos - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) { - exists(ReturnPosition pos | - fwdFlowReturnPosition(pos, cc, config) and - viableReturnPosOutEx(call, pos, out) and - not fullBarrier(out, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) { - fwdFlowOut(call, out, true, config) - } - - private predicate stateStepFwd(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1 | - additionalLocalStateStep(node1, state1, _, state2, config) or - additionalJumpStateStep(node1, state1, _, state2, config) - | - fwdFlow(node1, config) - ) - } - - private predicate fwdFlowState(FlowState state, Configuration config) { - sourceNode(_, state, config) - or - exists(FlowState state0 | - fwdFlowState(state0, config) and - stateStepFwd(state0, state, config) - ) - } - - /** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink. - */ - pragma[nomagic] - private predicate revFlow(NodeEx node, boolean toReturn, Configuration config) { - revFlow0(node, toReturn, config) and - fwdFlow(node, config) - } - - pragma[nomagic] - private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) { - exists(FlowState state | - fwdFlow(node, pragma[only_bind_into](config)) and - sinkNode(node, state, config) and - fwdFlowState(state, pragma[only_bind_into](config)) and - if hasSinkCallCtx(config) then toReturn = true else toReturn = false - ) - or - exists(NodeEx mid | revFlow(mid, toReturn, config) | - localFlowStep(node, mid, config) or - additionalLocalFlowStep(node, mid, config) or - additionalLocalStateStep(node, _, mid, _, config) - ) - or - exists(NodeEx mid | revFlow(mid, _, config) and toReturn = false | - jumpStep(node, mid, config) or - additionalJumpStep(node, mid, config) or - additionalJumpStateStep(node, _, mid, _, config) - ) - or - // store - exists(Content c | - revFlowStore(c, node, toReturn, config) and - revFlowConsCand(c, config) - ) - or - // read - exists(NodeEx mid, ContentSet c | - readSet(node, c, mid, config) and - fwdFlowConsCandSet(c, _, pragma[only_bind_into](config)) and - revFlow(mid, toReturn, pragma[only_bind_into](config)) - ) - or - // flow into a callable - revFlowIn(_, node, false, config) and - toReturn = false - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(pos, config) and - node.(RetNodeEx).getReturnPosition() = pos and - toReturn = true - ) - or - // flow through a callable - exists(DataFlowCall call | - revFlowInToReturn(call, node, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - /** - * Holds if `c` is the target of a read in the flow covered by `revFlow`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, ContentSet cs | - fwdFlow(node, pragma[only_bind_into](config)) and - readSet(node, cs, mid, config) and - fwdFlowConsCandSet(cs, c, pragma[only_bind_into](config)) and - revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) { - exists(NodeEx mid, TypedContent tc | - revFlow(mid, toReturn, pragma[only_bind_into](config)) and - fwdFlowConsCand(c, pragma[only_bind_into](config)) and - store(node, tc, mid, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `c` is the target of both a read and a store in the flow covered - * by `revFlow`. - */ - pragma[nomagic] - additional predicate revFlowIsReadAndStored(Content c, Configuration conf) { - revFlowConsCand(c, conf) and - revFlowStore(c, _, _, conf) - } - - pragma[nomagic] - additional predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config - ) { - fwdFlowReturnPosition(pos, _, config) and - viableReturnPosOutEx(call, pos, out) - } - - pragma[nomagic] - private predicate revFlowOut(ReturnPosition pos, Configuration config) { - exists(NodeEx out | - revFlow(out, _, config) and - viableReturnPosOutNodeCandFwd1(_, pos, out, config) - ) - } - - pragma[nomagic] - additional predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config - ) { - fwdFlowIn(call, arg, _, p, config) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate revFlowIn( - DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config - ) { - exists(ParamNodeEx p | - revFlow(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) { - revFlowIn(call, arg, true, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(NodeEx out | - revFlow(out, toReturn, config) and - fwdFlowOutFromArg(call, out, config) - ) - } - - private predicate stateStepRev(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - additionalLocalStateStep(node1, state1, node2, state2, config) or - additionalJumpStateStep(node1, state1, node2, state2, config) - | - revFlow(node1, _, pragma[only_bind_into](config)) and - revFlow(node2, _, pragma[only_bind_into](config)) and - fwdFlowState(state1, pragma[only_bind_into](config)) and - fwdFlowState(state2, pragma[only_bind_into](config)) - ) - } - - additional predicate revFlowState(FlowState state, Configuration config) { - exists(NodeEx node | - sinkNode(node, state, config) and - revFlow(node, _, pragma[only_bind_into](config)) and - fwdFlowState(state, pragma[only_bind_into](config)) - ) - or - exists(FlowState state0 | - revFlowState(state0, config) and - stateStepRev(state, state0, config) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Content c | - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - revFlow(node2, pragma[only_bind_into](config)) and - store(node1, tc, node2, contentType, config) and - c = tc.getContent() and - exists(ap1) - ) - } - - pragma[nomagic] - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) { - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - read(n1, c, n2, pragma[only_bind_into](config)) and - revFlow(n2, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, config) and - exists(ap) - } - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, _, pragma[only_bind_into](config)) and - exists(state) and - exists(ap) - } - - private predicate throughFlowNodeCand(NodeEx node, Configuration config) { - revFlow(node, true, config) and - fwdFlow(node, true, config) and - not inBarrier(node, config) and - not outBarrier(node, config) - } - - /** Holds if flow may return from `callable`. */ - pragma[nomagic] - private predicate returnFlowCallableNodeCand( - DataFlowCallable callable, ReturnKindExt kind, Configuration config - ) { - exists(RetNodeEx ret | - throughFlowNodeCand(ret, config) and - callable = ret.getEnclosingCallable() and - kind = ret.getKind() - ) - } - - /** - * Holds if flow may enter through `p` and reach a return node making `p` a - * candidate for the origin of a summary. - */ - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(DataFlowCallable c, ReturnKindExt kind | - throughFlowNodeCand(p, config) and - returnFlowCallableNodeCand(c, kind, config) and - p.getEnclosingCallable() = c and - exists(ap) and - parameterFlowThroughAllowed(p, kind) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - throughFlowNodeCand(ret, config) and - kind = ret.getKind() and - exists(argAp) and - exists(ap) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, boolean toReturn | - revFlow(arg, toReturn, config) and - revFlowInToReturn(call, arg, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, config)) and - fields = count(Content f0 | fwdFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | fwdFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, config)) and - fields = count(Content f0 | revFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | revFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | revFlow(n, b, config)) - } - /* End: Stage 1 logic. */ -} - -pragma[noinline] -private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - localFlowStep(node1, node2, config) -} - -pragma[noinline] -private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - additionalLocalFlowStep(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCand1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config -) { - Stage1::revFlow(out, config) and - Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, Configuration config -) { - exists(ReturnPosition pos | - viableReturnPosOutNodeCand1(call, pos, out, config) and - pos = ret.getReturnPosition() and - kind = pos.getKind() and - Stage1::revFlow(ret, config) and - not outBarrier(ret, config) and - not inBarrier(out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCand1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config -) { - Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and - Stage1::revFlow(arg, config) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config -) { - viableParamArgNodeCand1(call, p, arg, config) and - Stage1::revFlow(p, config) and - not outBarrier(arg, config) and - not inBarrier(p, config) -} - -/** - * Gets the amount of forward branching on the origin of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int branch(NodeEx n1, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n1, _, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) - ) -} - -/** - * Gets the amount of backward branching on the target of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int join(NodeEx n2, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n, _, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) - ) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. The - * `allowsFieldFlow` flag indicates whether the branching is within the limit - * specified by the configuration. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, ret, kind, out, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(ret, pragma[only_bind_into](config)) and - j = join(out, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. The `allowsFieldFlow` flag indicates whether - * the branching is within the limit specified by the configuration. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config -) { - flowIntoCallNodeCand1(call, arg, p, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(arg, pragma[only_bind_into](config)) and - j = join(p, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -private signature module StageSig { - class Ap; - - predicate revFlow(NodeEx node, Configuration config); - - predicate revFlowAp(NodeEx node, Ap ap, Configuration config); - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config); - - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config); - - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config); - - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ); - - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ); - - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config); -} - -private module MkStage { - class ApApprox = PrevStage::Ap; - - signature module StageParam { - class Ap; - - class ApNil extends Ap; - - bindingset[result, ap] - ApApprox getApprox(Ap ap); - - ApNil getApNil(NodeEx node); - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail); - - /** - * An approximation of `Content` that corresponds to the precision level of - * `Ap`, such that the mappings from both `Ap` and `Content` to this type - * are functional. - */ - class ApHeadContent; - - ApHeadContent getHeadContent(Ap ap); - - ApHeadContent projectToHeadContent(Content c); - - class ApOption; - - ApOption apNone(); - - ApOption apSome(Ap ap); - - class Cc; - - class CcCall extends Cc; - - // TODO: member predicate on CcCall - predicate matchesCall(CcCall cc, DataFlowCall call); - - class CcNoCall extends Cc; - - Cc ccNone(); - - CcCall ccSomeCall(); - - class LocalCc; - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc); - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ); - - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config - ); - - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config - ); - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config); - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType); - } - - module Stage implements StageSig { - import Param - - /* Begin: Stage logic. */ - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - private predicate revFlowApAlias(NodeEx node, ApApprox apa, Configuration config) { - PrevStage::revFlowAp(node, apa, config) - } - - pragma[nomagic] - private predicate flowIntoCallApa( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa, - Configuration config - ) { - flowIntoCall(call, arg, p, allowsFieldFlow, config) and - PrevStage::revFlowAp(p, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(arg, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowOutOfCallApa( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - ApApprox apa, Configuration config - ) { - flowOutOfCall(call, ret, kind, out, allowsFieldFlow, config) and - PrevStage::revFlowAp(out, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(ret, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowThroughOutOfCall( - DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, - ApApprox argApa, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa, pragma[only_bind_into](config)) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) and - matchesCall(ccc, call) - ) - } - - /** - * Holds if `node` is reachable with access path `ap` from a source in the - * configuration `config`. - * - * The call context `cc` records whether the node is reached through an - * argument in a call, and if so, `summaryCtx` and `argAp` record the - * corresponding parameter position and access path of that argument, respectively. - */ - pragma[nomagic] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa, config) and - PrevStage::revFlow(node, state, apa, config) and - filter(node, state, ap, config) - } - - pragma[inline] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, cc, summaryCtx, argAp, ap, _, config) - } - - pragma[nomagic] - private predicate fwdFlow0( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - sourceNode(node, state, config) and - (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and - argAp = apNone() and - summaryCtx = TParamNodeNone() and - ap = getApNil(node) and - apa = getApprox(ap) - or - exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | - fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0, config) and - localCc = getLocalCc(mid, cc) - | - localStep(mid, state0, node, state, true, _, config, localCc) and - ap = ap0 and - apa = apa0 - or - localStep(mid, state0, node, state, false, ap, config, localCc) and - ap0 instanceof ApNil and - apa = getApprox(ap) - ) - or - exists(NodeEx mid | - fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa, pragma[only_bind_into](config)) and - jumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(mid, state, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(mid, state0, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStateStep(mid, state0, node, state, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - // store - exists(TypedContent tc, Ap ap0 | - fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp, config) and - ap = apCons(tc, ap0) and - apa = getApprox(ap) - ) - or - // read - exists(Ap ap0, Content c | - fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp, config) and - fwdFlowConsCand(ap0, c, ap, config) and - apa = getApprox(ap) - ) - or - // flow into a callable - fwdFlowIn(_, node, state, _, cc, _, _, ap, apa, config) and - if PrevStage::parameterMayFlowThrough(node, apa, config) - then ( - summaryCtx = TParamNodeSome(node.asNode()) and - argAp = apSome(ap) - ) else ( - summaryCtx = TParamNodeNone() and argAp = apNone() - ) - or - // flow out of a callable - exists( - DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, - DataFlowCallable inner - | - fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa, config) and - flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa, config) and - inner = ret.getEnclosingCallable() and - cc = getCallContextReturn(inner, call, innercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - or - // flow through a callable - exists( - DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, ApApprox innerArgApa - | - fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowStore( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(DataFlowType contentType, ApApprox apa1 | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1, config) and - PrevStage::storeStepCand(node1, apa1, tc, node2, contentType, config) and - typecheckStore(ap1, contentType) - ) - } - - /** - * Holds if forward flow with access path `tail` reaches a store of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(TypedContent tc | - fwdFlowStore(_, tail, tc, _, _, _, _, _, config) and - tc.getContent() = c and - cons = apCons(tc, tail) - ) - } - - pragma[nomagic] - private predicate readStepCand( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - PrevStage::readStepCand(node1, c, node2, config) and - apc = projectToHeadContent(c) - } - - bindingset[node1, apc] - pragma[inline_late] - private predicate readStepCand0( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - readStepCand(node1, apc, c, node2, config) - } - - pragma[nomagic] - private predicate fwdFlowRead( - Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(ApHeadContent apc | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap, config) and - apc = getHeadContent(ap) and - readStepCand0(node1, apc, c, node2, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowIn( - DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, - ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa, Configuration config - ) { - exists(ArgNodeEx arg, boolean allowsFieldFlow | - fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa, config) and - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowRetFromArg( - RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, ApApprox argApa, - Ap ap, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - fwdFlow(pragma[only_bind_into](ret), state, ccc, - TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), - pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa), - pragma[only_bind_into](config)) and - kind = ret.getKind() and - parameterFlowThroughAllowed(summaryCtx, kind) and - argApa = getApprox(argAp) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) - ) - } - - pragma[inline] - private predicate fwdFlowThrough0( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, - Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa, config) and - fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp, config) - } - - pragma[nomagic] - private predicate fwdFlowThrough( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa, - config) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` - * and data might flow through the target callable and back out at `call`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered( - DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, - ParamNodeEx p, Ap ap, Configuration config - ) { - exists(ApApprox apa | - fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(p, apa, config) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate storeStepFwd( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config - ) { - fwdFlowStore(node1, ap1, tc, node2, _, _, _, _, config) and - ap2 = apCons(tc, ap1) and - fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _, config) - } - - private predicate readStepFwd( - NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config - ) { - fwdFlowRead(ap1, c, n1, n2, _, _, _, _, config) and - fwdFlowConsCand(ap1, c, ap2, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough0( - DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, - ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, - innerArgApa, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough( - RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, - Ap ap, Configuration config - ) { - exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | - returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa, config) and - pos = ret.getReturnPosition() and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowThroughIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap, - Configuration config - ) { - exists(ApApprox argApa | - flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), - allowsFieldFlow, argApa, pragma[only_bind_into](config)) and - fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa, - pragma[only_bind_into](config)) and - returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap, - pragma[only_bind_into](config)) and - if allowsFieldFlow = false then argAp instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowIntoCallAp( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap, - Configuration config - ) { - exists(ApApprox apa | - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - fwdFlow(arg, _, _, _, _, ap, apa, config) - ) - } - - pragma[nomagic] - private predicate flowOutOfCallAp( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, - Ap ap, Configuration config - ) { - exists(ApApprox apa | - flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa, config) and - fwdFlow(ret, _, _, _, _, ap, apa, config) and - pos = ret.getReturnPosition() - ) - } - - /** - * Holds if `node` with access path `ap` is part of a path from a source to a - * sink in the configuration `config`. - * - * The parameter `returnCtx` records whether (and how) the node must be returned - * from the enclosing callable in order to reach a sink, and if so, `returnAp` - * records the access path of the returned value. - */ - pragma[nomagic] - additional predicate revFlow( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - revFlow0(node, state, returnCtx, returnAp, ap, config) and - fwdFlow(node, state, _, _, _, ap, config) - } - - pragma[nomagic] - private predicate revFlow0( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, _, _, _, ap, config) and - sinkNode(node, state, config) and - ( - if hasSinkCallCtx(config) - then returnCtx = TReturnCtxNoFlowThrough() - else returnCtx = TReturnCtxNone() - ) and - returnAp = apNone() and - ap instanceof ApNil - or - exists(NodeEx mid, FlowState state0 | - localStep(node, state, mid, state0, true, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, ap, config) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap, pragma[only_bind_into](config)) and - localStep(node, pragma[only_bind_into](state), mid, state0, false, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, nil, pragma[only_bind_into](config)) and - ap instanceof ApNil - ) - or - exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, state, _, _, ap, config) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStep(node, mid, config) and - revFlow(pragma[only_bind_into](mid), state, _, _, nil, pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStateStep(node, state, mid, state0, config) and - revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil, - pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - // store - exists(Ap ap0, Content c | - revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp, config) and - revFlowConsCand(ap0, c, ap, config) - ) - or - // read - exists(NodeEx mid, Ap ap0 | - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - readStepFwd(node, ap, _, mid, ap0, config) - ) - or - // flow into a callable - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, state, TReturnCtxNone(), returnAp, ap, config) and - flowIntoCallAp(_, node, p, allowsFieldFlow, ap, config) and - (if allowsFieldFlow = false then ap instanceof ApNil else any()) and - returnCtx = TReturnCtxNone() - ) - or - // flow through a callable - exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, node, p, _, ap, innerReturnAp, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(_, node, pos, state, _, _, ap, config) and - if returnFlowsThrough(node, pos, state, _, _, _, ap, config) - then ( - returnCtx = TReturnCtxMaybeFlowThrough(pos) and - returnAp = apSome(ap) - ) else ( - returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() - ) - ) - } - - pragma[nomagic] - private predicate revFlowStore( - Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, - ReturnCtx returnCtx, ApOption returnAp, Configuration config - ) { - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - storeStepFwd(node, ap, tc, mid, ap0, config) and - tc.getContent() = c - } - - /** - * Holds if reverse flow with access path `tail` reaches a read of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(NodeEx mid, Ap tail0 | - revFlow(mid, _, _, _, tail, config) and - tail = pragma[only_bind_into](tail0) and - readStepFwd(_, cons, c, mid, tail0, config) - ) - } - - pragma[nomagic] - private predicate revFlowOut( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, - ApOption returnAp, Ap ap, Configuration config - ) { - exists(NodeEx out, boolean allowsFieldFlow | - revFlow(out, state, returnCtx, returnAp, ap, config) and - flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate revFlowParamToReturn( - ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap, Configuration config - ) { - revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), - pragma[only_bind_into](ap), pragma[only_bind_into](config)) and - parameterFlowThroughAllowed(p, pos.getKind()) and - PrevStage::parameterMayFlowThrough(p, getApprox(ap), config) - } - - pragma[nomagic] - private predicate revFlowThrough( - DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, - ApOption returnAp, Ap ap, Ap innerReturnAp, Configuration config - ) { - revFlowParamToReturn(p, state, pos, innerReturnAp, ap, config) and - revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned( - DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap, - Configuration config - ) { - exists(RetNodeEx ret, FlowState state, CcCall ccc | - revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap, config) and - returnFlowsThrough(ret, pos, state, ccc, _, _, ap, config) and - matchesCall(ccc, call) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Ap ap2, Content c | - PrevStage::storeStepCand(node1, _, tc, node2, contentType, config) and - revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _, config) and - revFlowConsCand(ap2, c, ap1, config) - ) - } - - predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(Ap ap1, Ap ap2 | - revFlow(node2, _, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and - readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _, - pragma[only_bind_into](config)) - ) - } - - additional predicate revFlow(NodeEx node, FlowState state, Configuration config) { - revFlow(node, state, _, _, _, config) - } - - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, _, _, ap, config) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, _, _, _, ap, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, Configuration config) { - revFlow(node, _, _, _, _, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, ap, config) - } - - private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepFwd(_, ap, tc, _, _, config) - } - - private predicate revConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepCand(_, ap, tc, _, _, config) - } - - private predicate validAp(Ap ap, Configuration config) { - revFlow(_, _, _, _, ap, config) and ap instanceof ApNil - or - exists(TypedContent head, Ap tail | - consCand(head, tail, config) and - ap = apCons(head, tail) - ) - } - - additional predicate consCand(TypedContent tc, Ap ap, Configuration config) { - revConsCand(tc, ap, config) and - validAp(ap, config) - } - - pragma[nomagic] - private predicate parameterFlowsThroughRev( - ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp, Configuration config - ) { - revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap, config) and - parameterFlowThroughAllowed(p, pos.getKind()) - } - - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(ReturnPosition pos | - returnFlowsThrough(_, pos, _, _, p, ap, _, config) and - parameterFlowsThroughRev(p, ap, pos, _, config) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - exists(ParamNodeEx p, ReturnPosition pos | - returnFlowsThrough(ret, pos, _, _, p, argAp, ap, config) and - parameterFlowsThroughRev(p, argAp, pos, ap, config) and - kind = pos.getKind() - ) - } - - pragma[nomagic] - private predicate revFlowThroughArg( - DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, - Ap ap, Configuration config - ) { - exists(ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp, config) - ) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | - revFlow(arg, state, returnCtx, returnAp, ap, config) and - revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, config)) and - fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and - states = count(FlowState state | fwdFlow(_, state, _, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | - fwdFlow(n, state, cc, summaryCtx, argAp, ap, config) - ) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, _, _, _, config)) and - fields = count(TypedContent f0 | consCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and - states = count(FlowState state | revFlow(_, state, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | - revFlow(n, state, returnCtx, retAp, ap, config) - ) - } - /* End: Stage logic. */ - } -} - -private module BooleanCallContext { - class Cc extends boolean { - Cc() { this in [true, false] } - } - - class CcCall extends Cc { - CcCall() { this = true } - } - - /** Holds if the call context may be `call`. */ - predicate matchesCall(CcCall cc, DataFlowCall call) { any() } - - class CcNoCall extends Cc { - CcNoCall() { this = false } - } - - Cc ccNone() { result = false } - - CcCall ccSomeCall() { result = true } - - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } -} - -private module Level1CallContext { - class Cc = CallContext; - - class CcCall = CallContextCall; - - pragma[inline] - predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } - - class CcNoCall = CallContextNoCall; - - Cc ccNone() { result instanceof CallContextAny } - - CcCall ccSomeCall() { result instanceof CallContextSomeCall } - - module NoLocalCallContext { - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSiteDispatch(call, c) - then result = TSpecificCall(call) - else result = TSomeCall() - } - } - - module LocalCallContext { - class LocalCc = LocalCallContext; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { - result = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - node.getEnclosingCallable()) - } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() - } - } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { - checkCallContextReturn(innercc, c, call) and - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() - } -} - -private module Stage2Param implements MkStage::StageParam { - private module PrevStage = Stage1; - - class Ap extends boolean { - Ap() { this in [true, false] } - } - - class ApNil extends Ap { - ApNil() { this = false } - } - - bindingset[result, ap] - PrevStage::Ap getApprox(Ap ap) { any() } - - ApNil getApNil(NodeEx node) { Stage1::revFlow(node, _) and exists(result) } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } - - class ApHeadContent = Unit; - - pragma[inline] - ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } - - ApHeadContent projectToHeadContent(Content c) { any() } - - class ApOption = BooleanOption; - - ApOption apNone() { result = TBooleanNone() } - - ApOption apSome(Ap ap) { result = TBooleanSome(ap) } - - import Level1CallContext - import NoLocalCallContext - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - ( - preservesValue = true and - localFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalStateStep(node1, state1, node2, state2, config) - ) and - exists(ap) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand1/6; - - predicate flowIntoCall = flowIntoCallNodeCand1/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - expectsContentEx(node, c) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - PrevStage::revFlowState(state, pragma[only_bind_into](config)) and - exists(ap) and - not stateBarrier(node, state, config) and - ( - notExpectsContent(node) - or - ap = true and - expectsContentCand(node, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage2 implements StageSig { - import MkStage::Stage -} - -pragma[nomagic] -private predicate flowOutOfCallNodeCand2( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -pragma[nomagic] -private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -private module LocalFlowBigStep { - /** - * A node where some checking is required, and hence the big-step relation - * is not allowed to step over. - */ - private class FlowCheckNode extends NodeEx { - FlowCheckNode() { - castNode(this.asNode()) or - clearsContentCached(this.asNode(), _) or - expectsContentCached(this.asNode(), _) - } - } - - /** - * Holds if `node` can be the first node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowEntry(NodeEx node, FlowState state, Configuration config) { - Stage2::revFlow(node, state, config) and - ( - sourceNode(node, state, config) - or - jumpStep(_, node, config) - or - additionalJumpStep(_, node, config) - or - additionalJumpStateStep(_, _, node, state, config) - or - node instanceof ParamNodeEx - or - node.asNode() instanceof OutNodeExt - or - Stage2::storeStepCand(_, _, _, node, _, config) - or - Stage2::readStepCand(_, _, node, config) - or - node instanceof FlowCheckNode - or - exists(FlowState s | - additionalLocalStateStep(_, s, node, state, config) and - s != state - ) - ) - } - - /** - * Holds if `node` can be the last node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowExit(NodeEx node, FlowState state, Configuration config) { - exists(NodeEx next | Stage2::revFlow(next, state, config) | - jumpStep(node, next, config) or - additionalJumpStep(node, next, config) or - flowIntoCallNodeCand2(_, node, next, _, config) or - flowOutOfCallNodeCand2(_, node, _, next, _, config) or - Stage2::storeStepCand(node, _, _, next, _, config) or - Stage2::readStepCand(node, _, next, config) - ) - or - exists(NodeEx next, FlowState s | Stage2::revFlow(next, s, config) | - additionalJumpStateStep(node, state, next, s, config) - or - additionalLocalStateStep(node, state, next, s, config) and - s != state - ) - or - Stage2::revFlow(node, state, config) and - node instanceof FlowCheckNode - or - sinkNode(node, state, config) - } - - pragma[noinline] - private predicate additionalLocalFlowStepNodeCand2( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, Configuration config - ) { - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 and - Stage2::revFlow(node1, pragma[only_bind_into](state1), false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false, - pragma[only_bind_into](config)) - or - additionalLocalStateStep(node1, state1, node2, state2, config) and - Stage2::revFlow(node1, state1, false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, state2, false, pragma[only_bind_into](config)) - } - - /** - * Holds if the local path from `node1` to `node2` is a prefix of a maximal - * subsequence of local flow steps in a dataflow path. - * - * This is the transitive closure of `[additional]localFlowStep` beginning - * at `localFlowEntry`. - */ - pragma[nomagic] - private predicate localFlowStepPlus( - NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, - Configuration config, LocalCallContext cc - ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and - ( - localFlowEntry(node1, pragma[only_bind_into](state), pragma[only_bind_into](config)) and - ( - localFlowStepNodeCand1(node1, node2, config) and - preservesValue = true and - t = node1.getDataFlowType() and // irrelevant dummy value - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - or - additionalLocalFlowStepNodeCand2(node1, state, node2, state, config) and - preservesValue = false and - t = node2.getDataFlowType() - ) and - node1 != node2 and - cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) - or - exists(NodeEx mid | - localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, - pragma[only_bind_into](config), cc) and - localFlowStepNodeCand1(mid, node2, config) and - not mid instanceof FlowCheckNode and - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - ) - or - exists(NodeEx mid | - localFlowStepPlus(node1, state, mid, _, _, pragma[only_bind_into](config), cc) and - additionalLocalFlowStepNodeCand2(mid, state, node2, state, config) and - not mid instanceof FlowCheckNode and - preservesValue = false and - t = node2.getDataFlowType() - ) - ) - } - - /** - * Holds if `node1` can step to `node2` in one or more local steps and this - * path can occur as a maximal subsequence of local steps in a dataflow path. - */ - pragma[nomagic] - predicate localFlowBigStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - DataFlowType t, Configuration config, LocalCallContext callContext - ) { - localFlowStepPlus(node1, state1, node2, preservesValue, t, config, callContext) and - localFlowExit(node2, state1, config) and - state1 = state2 - or - additionalLocalFlowStepNodeCand2(node1, state1, node2, state2, config) and - state1 != state2 and - preservesValue = false and - t = node2.getDataFlowType() and - callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) - } -} - -private import LocalFlowBigStep - -private module Stage3Param implements MkStage::StageParam { - private module PrevStage = Stage2; - - class Ap = ApproxAccessPathFront; - - class ApNil = ApproxAccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } - - class ApHeadContent = ContentApprox; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - predicate projectToHeadContent = getContentApprox/1; - - class ApOption = ApproxAccessPathFrontOption; - - ApOption apNone() { result = TApproxAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } - - import BooleanCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApproxAccessPathFrontNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand2/6; - - predicate flowIntoCall = flowIntoCallNodeCand2/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getAHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage3 implements StageSig { - import MkStage::Stage -} - -private module Stage4Param implements MkStage::StageParam { - private module PrevStage = Stage3; - - class Ap = AccessPathFront; - - class ApNil = AccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathFrontOption; - - ApOption apNone() { result = TAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } - - import BooleanCallContext - - pragma[nomagic] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) and - exists(lcc) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clearSet(NodeEx node, ContentSet c, Configuration config) { - PrevStage::revFlow(node, config) and - clearsContentCached(node.asNode(), c) - } - - pragma[nomagic] - private predicate clearContent(NodeEx node, Content c, Configuration config) { - exists(ContentSet cs | - PrevStage::readStepCand(_, pragma[only_bind_into](c), _, pragma[only_bind_into](config)) and - c = cs.getAReadContent() and - clearSet(node, cs, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clear(NodeEx node, Ap ap, Configuration config) { - clearContent(node, ap.getHead().getContent(), config) - } - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - not clear(node, ap, config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage4 implements StageSig { - import MkStage::Stage -} - -/** - * Holds if `argApf` is recorded as the summary context for flow reaching `node` - * and remains relevant for the following pruning stage. - */ -private predicate flowCandSummaryCtx( - NodeEx node, FlowState state, AccessPathFront argApf, Configuration config -) { - exists(AccessPathFront apf | - Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf, config) and - Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf, - config) - ) -} - -/** - * Holds if a length 2 access path approximation with the head `tc` is expected - * to be expensive. - */ -private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { - exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf, config)) and - nodes = - strictcount(NodeEx n, FlowState state | - Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - or - flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - ) and - accessPathApproxCostLimits(apLimit, tupleLimit) and - apLimit < tails and - tupleLimit < (tails - 1) * nodes and - not tc.forceHighPrecision() - ) -} - -private newtype TAccessPathApprox = - TNil(DataFlowType t) or - TConsNil(TypedContent tc, DataFlowType t) { - Stage4::consCand(tc, TFrontNil(t), _) and - not expensiveLen2unfolding(tc, _) - } or - TConsCons(TypedContent tc1, TypedContent tc2, int len) { - Stage4::consCand(tc1, TFrontHead(tc2), _) and - len in [2 .. accessPathLimit()] and - not expensiveLen2unfolding(tc1, _) - } or - TCons1(TypedContent tc, int len) { - len in [1 .. accessPathLimit()] and - expensiveLen2unfolding(tc, _) - } - -/** - * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only - * the first two elements of the list and its length are tracked. If data flows - * from a source to a given node with a given `AccessPathApprox`, this indicates - * the sequence of dereference operations needed to get from the value in the node - * to the tracked object. The final type indicates the type of the tracked object. - */ -abstract private class AccessPathApprox extends TAccessPathApprox { - abstract string toString(); - - abstract TypedContent getHead(); - - abstract int len(); - - abstract DataFlowType getType(); - - abstract AccessPathFront getFront(); - - /** Gets the access path obtained by popping `head` from this path, if any. */ - abstract AccessPathApprox pop(TypedContent head); -} - -private class AccessPathApproxNil extends AccessPathApprox, TNil { - private DataFlowType t; - - AccessPathApproxNil() { this = TNil(t) } - - override string toString() { result = concat(": " + ppReprType(t)) } - - override TypedContent getHead() { none() } - - override int len() { result = 0 } - - override DataFlowType getType() { result = t } - - override AccessPathFront getFront() { result = TFrontNil(t) } - - override AccessPathApprox pop(TypedContent head) { none() } -} - -abstract private class AccessPathApproxCons extends AccessPathApprox { } - -private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { - private TypedContent tc; - private DataFlowType t; - - AccessPathApproxConsNil() { this = TConsNil(tc, t) } - - override string toString() { - // The `concat` becomes "" if `ppReprType` has no result. - result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) - } - - override TypedContent getHead() { result = tc } - - override int len() { result = 1 } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } -} - -private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { - private TypedContent tc1; - private TypedContent tc2; - private int len; - - AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - - override string toString() { - if len = 2 - then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" - else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc1 } - - override int len() { result = len } - - override DataFlowType getType() { result = tc1.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc1) } - - override AccessPathApprox pop(TypedContent head) { - head = tc1 and - ( - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - } -} - -private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { - private TypedContent tc; - private int len; - - AccessPathApproxCons1() { this = TCons1(tc, len) } - - override string toString() { - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc } - - override int len() { result = len } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { - head = tc and - ( - exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2), _) | - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - or - exists(DataFlowType t | - len = 1 and - Stage4::consCand(tc, TFrontNil(t), _) and - result = TNil(t) - ) - ) - } -} - -/** Gets the access path obtained by popping `tc` from `ap`, if any. */ -private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } - -/** Gets the access path obtained by pushing `tc` onto `ap`. */ -private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } - -private newtype TAccessPathApproxOption = - TAccessPathApproxNone() or - TAccessPathApproxSome(AccessPathApprox apa) - -private class AccessPathApproxOption extends TAccessPathApproxOption { - string toString() { - this = TAccessPathApproxNone() and result = "" - or - this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) - } -} - -private module Stage5Param implements MkStage::StageParam { - private module PrevStage = Stage4; - - class Ap = AccessPathApprox; - - class ApNil = AccessPathApproxNil; - - pragma[nomagic] - PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathApproxOption; - - ApOption apNone() { result = TAccessPathApproxNone() } - - ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } - - import Level1CallContext - import LocalCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, lcc) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { any() } - - // Type checking is not necessary here as it has already been done in stage 3. - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage5 = MkStage::Stage; - -bindingset[conf, result] -private Configuration unbindConf(Configuration conf) { - exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c)) -} - -pragma[nomagic] -private predicate nodeMayUseSummary0( - NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(AccessPathApprox apa0 | - Stage5::parameterMayFlowThrough(p, _, _) and - Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0, config) and - Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), - TAccessPathApproxSome(apa), apa0, config) - ) -} - -pragma[nomagic] -private predicate nodeMayUseSummary( - NodeEx n, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::parameterMayFlowThrough(p, apa, config) and - nodeMayUseSummary0(n, p, state, apa, config) - ) -} - -private newtype TSummaryCtx = - TSummaryCtxNone() or - TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { - exists(Configuration config | - Stage5::parameterMayFlowThrough(p, ap.getApprox(), config) and - Stage5::revFlow(p, state, _, config) - ) - } - -/** - * A context for generating flow summaries. This represents flow entry through - * a specific parameter with an access path of a specific shape. - * - * Summaries are only created for parameters that may flow through. - */ -abstract private class SummaryCtx extends TSummaryCtx { - abstract string toString(); -} - -/** A summary context from which no flow summary can be generated. */ -private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { - override string toString() { result = "" } -} - -/** A summary context from which a flow summary can be generated. */ -private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParamNodeEx p; - private FlowState s; - private AccessPath ap; - - SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } - - ParameterPosition getParameterPos() { p.isParameterOf(_, result) } - - ParamNodeEx getParamNode() { result = p } - - override string toString() { result = p + ": " + ap } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** - * Gets the number of length 2 access path approximations that correspond to `apa`. - */ -private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { - exists(TypedContent tc, int len | - tc = apa.getHead() and - len = apa.len() and - result = - strictcount(AccessPathFront apf | - Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), - config) - ) - ) -} - -private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = - strictcount(NodeEx n, FlowState state | - Stage5::revFlow(n, state, apa, config) or nodeMayUseSummary(n, state, apa, config) - ) -} - -/** - * Holds if a length 2 access path approximation matching `apa` is expected - * to be expensive. - */ -private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = count1to2unfold(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - apLimit < aps and - tupleLimit < (aps - 1) * nodes - ) -} - -private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { - exists(TypedContent head | - apa.pop(head) = result and - Stage5::consCand(head, result, config) - ) -} - -/** - * Holds with `unfold = false` if a precise head-tail representation of `apa` is - * expected to be expensive. Holds with `unfold = true` otherwise. - */ -private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { - if apa.getHead().forceHighPrecision() - then unfold = true - else - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = countPotentialAps(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true - ) -} - -/** - * Gets the number of `AccessPath`s that correspond to `apa`. - */ -pragma[assume_small_delta] -private int countAps(AccessPathApprox apa, Configuration config) { - evalUnfold(apa, false, config) and - result = 1 and - (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) - or - evalUnfold(apa, false, config) and - result = count1to2unfold(apa, config) and - not expensiveLen1to2unfolding(apa, config) - or - evalUnfold(apa, true, config) and - result = countPotentialAps(apa, config) -} - -/** - * Gets the number of `AccessPath`s that would correspond to `apa` assuming - * that it is expanded to a precise head-tail representation. - */ -language[monotonicAggregates] -pragma[assume_small_delta] -private int countPotentialAps(AccessPathApprox apa, Configuration config) { - apa instanceof AccessPathApproxNil and result = 1 - or - result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) -} - -private newtype TAccessPath = - TAccessPathNil(DataFlowType t) or - TAccessPathCons(TypedContent head, AccessPath tail) { - exists(AccessPathApproxCons apa | - not evalUnfold(apa, false, _) and - head = apa.getHead() and - tail.getApprox() = getATail(apa, _) - ) - } or - TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - not expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head1 = apa.getHead() and - head2 = getATail(apa, _).getHead() - ) - } or - TAccessPathCons1(TypedContent head, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head = apa.getHead() - ) - } - -private newtype TPathNode = - pragma[assume_small_delta] - TPathNodeMid( - NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config - ) { - // A PathNode is introduced by a source ... - Stage5::revFlow(node, state, config) and - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - or - // ... or a step from an existing PathNode to another node. - exists(PathNodeMid mid | - pathStep(mid, node, state, cc, sc, ap) and - pragma[only_bind_into](config) = mid.getConfiguration() and - Stage5::revFlow(node, state, ap.getApprox(), pragma[only_bind_into](config)) - ) - } or - TPathNodeSink(NodeEx node, FlowState state, Configuration config) { - exists(PathNodeMid sink | - sink.isAtSink() and - node = sink.getNodeEx() and - state = sink.getState() and - config = sink.getConfiguration() - ) - } or - TPathNodeSourceGroup(string sourceGroup, Configuration config) { - exists(PathNodeImpl source | - sourceGroup = source.getSourceGroup() and - config = source.getConfiguration() - ) - } or - TPathNodeSinkGroup(string sinkGroup, Configuration config) { - exists(PathNodeSink sink | - sinkGroup = sink.getSinkGroup() and - config = sink.getConfiguration() - ) - } - -/** - * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a - * source to a given node with a given `AccessPath`, this indicates the sequence - * of dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ -private class AccessPath extends TAccessPath { - /** Gets the head of this access path, if any. */ - abstract TypedContent getHead(); - - /** Gets the tail of this access path, if any. */ - abstract AccessPath getTail(); - - /** Gets the front of this access path. */ - abstract AccessPathFront getFront(); - - /** Gets the approximation of this access path. */ - abstract AccessPathApprox getApprox(); - - /** Gets the length of this access path. */ - abstract int length(); - - /** Gets a textual representation of this access path. */ - abstract string toString(); - - /** Gets the access path obtained by popping `tc` from this access path, if any. */ - final AccessPath pop(TypedContent tc) { - result = this.getTail() and - tc = this.getHead() - } - - /** Gets the access path obtained by pushing `tc` onto this access path. */ - final AccessPath push(TypedContent tc) { this = result.pop(tc) } -} - -private class AccessPathNil extends AccessPath, TAccessPathNil { - private DataFlowType t; - - AccessPathNil() { this = TAccessPathNil(t) } - - DataFlowType getType() { result = t } - - override TypedContent getHead() { none() } - - override AccessPath getTail() { none() } - - override AccessPathFrontNil getFront() { result = TFrontNil(t) } - - override AccessPathApproxNil getApprox() { result = TNil(t) } - - override int length() { result = 0 } - - override string toString() { result = concat(": " + ppReprType(t)) } -} - -private class AccessPathCons extends AccessPath, TAccessPathCons { - private TypedContent head; - private AccessPath tail; - - AccessPathCons() { this = TAccessPathCons(head, tail) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { result = tail } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - pragma[assume_small_delta] - override AccessPathApproxCons getApprox() { - result = TConsNil(head, tail.(AccessPathNil).getType()) - or - result = TConsCons(head, tail.getHead(), this.length()) - or - result = TCons1(head, this.length()) - } - - pragma[assume_small_delta] - override int length() { result = 1 + tail.length() } - - private string toStringImpl(boolean needsSuffix) { - exists(DataFlowType t | - tail = TAccessPathNil(t) and - needsSuffix = false and - result = head.toString() + "]" + concat(" : " + ppReprType(t)) - ) - or - result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) - or - exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | - result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true - or - result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false - ) - or - exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | - result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true - or - result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false - ) - } - - override string toString() { - result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" - or - result = "[" + this.toStringImpl(false) - } -} - -private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { - private TypedContent head1; - private TypedContent head2; - private int len; - - AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } - - override TypedContent getHead() { result = head1 } - - override AccessPath getTail() { - Stage5::consCand(head1, result.getApprox(), _) and - result.getHead() = head2 and - result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head1) } - - override AccessPathApproxCons getApprox() { - result = TConsCons(head1, head2, len) or - result = TCons1(head1, len) - } - - override int length() { result = len } - - override string toString() { - if len = 2 - then result = "[" + head1.toString() + ", " + head2.toString() + "]" - else - result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" - } -} - -private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { - private TypedContent head; - private int len; - - AccessPathCons1() { this = TAccessPathCons1(head, len) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { - Stage5::consCand(head, result.getApprox(), _) and result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - - override int length() { result = len } - - override string toString() { - if len = 1 - then result = "[" + head.toString() + "]" - else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" - } -} - -abstract private class PathNodeImpl extends TPathNode { - /** Gets the `FlowState` of this node. */ - abstract FlowState getState(); - - /** Gets the associated configuration. */ - abstract Configuration getConfiguration(); - - /** Holds if this node is a source. */ - abstract predicate isSource(); - - abstract PathNodeImpl getASuccessorImpl(); - - private PathNodeImpl getASuccessorIfHidden() { - this.isHidden() and - result = this.getASuccessorImpl() - } - - pragma[nomagic] - private PathNodeImpl getANonHiddenSuccessor0() { - result = this.getASuccessorIfHidden*() and - not result.isHidden() - } - - final PathNodeImpl getANonHiddenSuccessor() { - result = this.getASuccessorImpl().getANonHiddenSuccessor0() and - not this.isHidden() - } - - abstract NodeEx getNodeEx(); - - predicate isHidden() { - not this.getConfiguration().includeHiddenNodes() and - ( - hiddenNode(this.getNodeEx().asNode()) and - not this.isSource() and - not this instanceof PathNodeSink - or - this.getNodeEx() instanceof TNodeImplicitRead - ) - } - - string getSourceGroup() { - this.isSource() and - this.getConfiguration().sourceGrouping(this.getNodeEx().asNode(), result) + not singleConfiguration() and + getConfig(state1).isAdditionalFlowStep(node1, node2) and + state2 = state1 } - predicate isFlowSource() { - this.isSource() and not exists(this.getSourceGroup()) - or - this instanceof PathNodeSourceGroup + predicate allowImplicitRead(Node node, ContentSet c) { + any(Configuration config).allowImplicitRead(node, c) } - predicate isFlowSink() { - this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or - this instanceof PathNodeSinkGroup - } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } - private string ppAp() { - this instanceof PathNodeSink and result = "" - or - exists(string s | s = this.(PathNodeMid).getAp().toString() | - if s = "" then result = "" else result = " " + s - ) - } + FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } - private string ppCtx() { - this instanceof PathNodeSink and result = "" - or - result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + predicate sourceGrouping(Node source, string sourceGroup) { + any(Configuration config).sourceGrouping(source, sourceGroup) } - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + predicate sinkGrouping(Node sink, string sinkGroup) { + any(Configuration config).sinkGrouping(sink, sinkGroup) } -} - -/** Holds if `n` can reach a sink. */ -private predicate directReach(PathNodeImpl n) { - n instanceof PathNodeSink or - n instanceof PathNodeSinkGroup or - directReach(n.getANonHiddenSuccessor()) -} - -/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ -private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } -/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { - n1.getANonHiddenSuccessor() = n2 and directReach(n2) + predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() } } -private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) +private import Impl as I +import I /** * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. */ -class PathNode instanceof PathNodeImpl { - PathNode() { reach(this) } - +class PathNode instanceof I::PathNode { /** Gets a textual representation of this element. */ final string toString() { result = super.toString() } @@ -3406,1548 +358,39 @@ class PathNode instanceof PathNodeImpl { } /** Gets the underlying `Node`. */ - final Node getNode() { super.getNodeEx().projectToNode() = result } + final Node getNode() { result = super.getNode() } /** Gets the `FlowState` of this node. */ - final FlowState getState() { result = super.getState() } + final FlowState getState() { result = getState(super.getState()) } /** Gets the associated configuration. */ - final Configuration getConfiguration() { result = super.getConfiguration() } + final Configuration getConfiguration() { result = getConfig(super.getState()) } /** Gets a successor of this node, if any. */ - final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } + final PathNode getASuccessor() { result = super.getASuccessor() } /** Holds if this node is a source. */ final predicate isSource() { super.isSource() } /** Holds if this node is a grouping of source nodes. */ - final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group, _) } + final predicate isSourceGroup(string group) { super.isSourceGroup(group) } /** Holds if this node is a grouping of sink nodes. */ - final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group, _) } + final predicate isSinkGroup(string group) { super.isSinkGroup(group) } } -/** - * Provides the query predicates needed to include a graph in a path-problem query. - */ -module PathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } - - /** Holds if `n` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode n, string key, string val) { - key = "semmle.label" and val = n.toString() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { - Subpaths::subpaths(arg, par, ret, out) - } -} - -/** - * An intermediate flow graph node. This is a triple consisting of a `Node`, - * a `CallContext`, and a `Configuration`. - */ -private class PathNodeMid extends PathNodeImpl, TPathNodeMid { - NodeEx node; - FlowState state; - CallContext cc; - SummaryCtx sc; - AccessPath ap; - Configuration config; - - PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - SummaryCtx getSummaryCtx() { result = sc } - - AccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - private PathNodeMid getSuccMid() { - pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx(), result.getAp()) and - result.getConfiguration() = unbindConf(this.getConfiguration()) - } - - override PathNodeImpl getASuccessorImpl() { - // an intermediate step to another intermediate node - result = this.getSuccMid() - or - // a final step to a sink - result = this.getSuccMid().projectToSink() - } - - override predicate isSource() { - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - } - - predicate isAtSink() { - sinkNode(node, state, config) and - ap instanceof AccessPathNil and - if hasSinkCallCtx(config) - then - // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` - // is exactly what we need to check. This also implies - // `sc instanceof SummaryCtxNone`. - // For `FeatureEqualSourceSinkCallContext` the initial call context was - // set to `CallContextSomeCall` and jumps are disallowed, so - // `cc instanceof CallContextNoCall` never holds. On the other hand, - // in this case there's never any need to enter a call except to identify - // a summary, so the condition in `pathIntoCallable` enforces this, which - // means that `sc instanceof SummaryCtxNone` holds if and only if we are - // in the call context of the source. - sc instanceof SummaryCtxNone or - cc instanceof CallContextNoCall - else any() - } - - PathNodeSink projectToSink() { - this.isAtSink() and - result.getNodeEx() = node and - result.getState() = state and - result.getConfiguration() = unbindConf(config) - } -} - -/** - * A flow graph node corresponding to a sink. This is disjoint from the - * intermediate nodes in order to uniquely correspond to a given sink by - * excluding the `CallContext`. - */ -private class PathNodeSink extends PathNodeImpl, TPathNodeSink { - NodeEx node; - FlowState state; - Configuration config; - - PathNodeSink() { this = TPathNodeSink(node, state, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result = TPathNodeSinkGroup(this.getSinkGroup(), config) - } - - override predicate isSource() { sourceNode(node, state, config) } - - string getSinkGroup() { config.sinkGrouping(node.asNode(), result) } -} - -private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { - string sourceGroup; - Configuration config; - - PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result.getSourceGroup() = sourceGroup and - result.getConfiguration() = config - } - - override predicate isSource() { none() } - - override string toString() { result = sourceGroup } - - override 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 - } -} - -private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { - string sinkGroup; - Configuration config; - - PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { none() } - - override predicate isSource() { none() } - - override string toString() { result = sinkGroup } - - override 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 - } -} - -private predicate pathNode( - PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, - Configuration conf, LocalCallContext localCC -) { - midnode = mid.getNodeEx() and - state = mid.getState() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - midnode.getEnclosingCallable()) and - ap = mid.getAp() -} - -/** - * Holds if data may flow from `mid` to `node`. The last step in or out of - * a callable is recorded by `cc`. - */ -pragma[assume_small_delta] -pragma[nomagic] -private predicate pathStep( - PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap -) { - exists(NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC | - pathNode(mid, midnode, state0, cc, sc, ap, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, true, _, conf, localCC) - ) - or - exists( - AccessPath ap0, NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC - | - pathNode(mid, midnode, state0, cc, sc, ap0, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), conf, - localCC) and - ap0 instanceof AccessPathNil - ) - or - jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - ap = mid.getAp() - or - additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, mid.getConfiguration()) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - pathIntoCallable(mid, node, state, _, cc, sc, _, _) and ap = mid.getAp() - or - pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone - or - pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() -} - -pragma[nomagic] -private predicate pathReadStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - tc = ap0.getHead() and - Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -pragma[nomagic] -private predicate pathStoreStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -private predicate pathOutOfCallable0( - PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa, - Configuration config -) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - apa = mid.getAp().getApprox() and - config = mid.getConfiguration() -} - -pragma[nomagic] -private predicate pathOutOfCallable1( - PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPathApprox apa, Configuration config -) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - pathOutOfCallable0(mid, pos, state, innercc, apa, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() +private predicate hasFlow(Node source, Node sink, Configuration config) { + exists(PathNode source0, PathNode sink0 | + hasFlowPath(source0, sink0, config) and + source0.getNode() = source and + sink0.getNode() = sink ) } -pragma[noinline] -private NodeEx getAnOutNodeFlow( - ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config -) { - result.asNode() = kind.getAnOutNode(call) and - Stage5::revFlow(result, _, apa, config) +private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) { + hasFlowPath(source, sink) and source.getConfiguration() = config } -/** - * Holds if data may flow from `mid` to `out`. The last step of this path - * is a return from a callable and is recorded by `cc`, if needed. - */ -pragma[noinline] -private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { - exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | - pathOutOfCallable1(mid, call, kind, state, cc, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} +private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) } -/** - * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. - */ -pragma[noinline] -private predicate pathIntoArg( - PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(ArgNodeEx arg, ArgumentPosition apos | - pathNode(mid, arg, state, cc, _, ap, config, _) and - arg.asNode().(ArgNode).argumentOf(call, apos) and - apa = ap.getApprox() and - parameterMatch(ppos, apos) - ) -} - -pragma[nomagic] -private predicate parameterCand( - DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::revFlow(p, _, apa, config) and - p.isParameterOf(callable, pos) - ) -} - -pragma[nomagic] -private predicate pathIntoCallable0( - PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, AccessPath ap, Configuration config -) { - exists(AccessPathApprox apa | - pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - callable = resolveCall(call, outercc) and - parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa), - pragma[only_bind_into](config)) - ) -} - -/** - * Holds if data may flow from `mid` to `p` through `call`. The contexts - * before and after entering the callable are `outercc` and `innercc`, - * respectively. - */ -pragma[nomagic] -private predicate pathIntoCallable( - PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, - SummaryCtx sc, DataFlowCall call, Configuration config -) { - exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | - pathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - ( - sc = TSummaryCtxSome(p, state, ap) - or - not exists(TSummaryCtxSome(p, state, ap)) and - sc = TSummaryCtxNone() and - // When the call contexts of source and sink needs to match then there's - // never any reason to enter a callable except to find a summary. See also - // the comment in `PathNodeMid::isAtSink`. - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) -} - -/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ -pragma[nomagic] -private predicate paramFlowsThrough( - ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, - AccessPathApprox apa, Configuration config -) { - exists(RetNodeEx ret | - pathNode(_, ret, state, cc, sc, ap, config, _) and - kind = ret.getKind() and - apa = ap.getApprox() and - parameterFlowThroughAllowed(sc.getParamNode(), kind) - ) -} - -pragma[nomagic] -private predicate pathThroughCallable0( - DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(CallContext innercc, SummaryCtx sc | - pathIntoCallable(mid, _, _, cc, innercc, sc, call, config) and - paramFlowsThrough(kind, state, innercc, sc, ap, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` through a callable to the node `out`. - * The context `cc` is restored to its value prior to entering the callable. - */ -pragma[noinline] -private predicate pathThroughCallable( - PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap -) { - exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config | - pathThroughCallable0(call, mid, kind, state, cc, ap, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -private module Subpaths { - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths01( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - exists(Configuration config | - pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and - pathIntoCallable(arg, par, _, _, innercc, sc, _, config) and - paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, - pragma[only_bind_into](apout), _, unbindConf(config)) and - not arg.isHidden() - ) - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `sout`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths02( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and - out.asNode() = kind.getAnOutNode(_) - } - - pragma[nomagic] - private Configuration getPathNodeConf(PathNodeImpl n) { result = n.getConfiguration() } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple. - */ - pragma[nomagic] - private predicate subpaths03( - PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, AccessPath apout - ) { - exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | - subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and - pathNode(ret, retnode, sout, innercc, sc, apout, unbindConf(getPathNodeConf(arg)), _) and - kind = retnode.getKind() - ) - } - - private PathNodeImpl localStepToHidden(PathNodeImpl n) { - n.getASuccessorImpl() = result and - result.isHidden() and - exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | - localFlowBigStep(n1, _, n2, _, _, _, _, _) or - store(n1, _, n2, _, _) or - readSet(n1, _, n2, _) - ) - } - - pragma[nomagic] - private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { - succ = pred.getANonHiddenSuccessor() and - succNode = succ.getNodeEx() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { - exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | - pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and - subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and - hasSuccessor(pragma[only_bind_into](arg), par, p) and - not ret.isHidden() and - pathNode(out0, o, sout, _, _, apout, _, _) - | - out = out0 or out = out0.projectToSink() - ) - } - - /** - * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. - */ - predicate retReach(PathNodeImpl n) { - exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) - or - exists(PathNodeImpl mid | - retReach(mid) and - n.getANonHiddenSuccessor() = mid and - not subpaths(_, mid, _, _) - ) - } -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -private predicate hasFlowPath( - PathNodeImpl flowsource, PathNodeImpl flowsink, Configuration configuration -) { - flowsource.isFlowSource() and - flowsource.getConfiguration() = configuration and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.isFlowSink() -} - -private predicate flowsTo( - PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink, - Configuration configuration -) { - flowsource.isSource() and - flowsource.getConfiguration() = configuration and - flowsource.getNodeEx().asNode() = source and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.getNodeEx().asNode() = sink -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -predicate flowsTo(Node source, Node sink, Configuration configuration) { - flowsTo(_, _, source, sink, configuration) -} - -private predicate finalStats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples -) { - fwd = true and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and - tuples = count(PathNodeImpl pn) - or - fwd = false and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and - tuples = count(PathNode pn) -} - -/** - * INTERNAL: Only for debugging. - * - * Calculates per-stage metrics for data flow. - */ -predicate stageStats( - int n, string stage, int nodes, int fields, int conscand, int states, int tuples, - Configuration config -) { - stage = "1 Fwd" and - n = 10 and - Stage1::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "1 Rev" and - n = 15 and - Stage1::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Fwd" and - n = 20 and - Stage2::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Rev" and - n = 25 and - Stage2::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Fwd" and - n = 30 and - Stage3::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Rev" and - n = 35 and - Stage3::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Fwd" and - n = 40 and - Stage4::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Rev" and - n = 45 and - Stage4::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Fwd" and - n = 50 and - Stage5::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Rev" and - n = 55 and - Stage5::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) - or - stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) -} - -private module FlowExploration { - private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - jumpStep(node1, node2, config) - or - additionalJumpStep(node1, node2, config) - or - additionalJumpStateStep(node1, _, node2, _, config) - or - // flow into callable - viableParamArgEx(_, node2, node1) - or - // flow out of a callable - viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) - | - c1 = node1.getEnclosingCallable() and - c2 = node2.getEnclosingCallable() and - c1 != c2 - ) - } - - private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSource(n) or config.isSource(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSrc(mid, config) and callableStep(mid, c, config) - ) - } - - private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSink(n) or config.isSink(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSink(mid, config) and callableStep(c, mid, config) - ) - } - - private newtype TCallableExt = - TCallable(DataFlowCallable c, Configuration config) { - interestingCallableSrc(c, config) or - interestingCallableSink(c, config) - } or - TCallableSrc() or - TCallableSink() - - private predicate callableExtSrc(TCallableSrc src) { any() } - - private predicate callableExtSink(TCallableSink sink) { any() } - - private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { - exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | - callableStep(c1, c2, config) and - ce1 = TCallable(c1, pragma[only_bind_into](config)) and - ce2 = TCallable(c2, pragma[only_bind_into](config)) - ) - or - exists(Node n, Configuration config | - ce1 = TCallableSrc() and - (config.isSource(n) or config.isSource(n, _)) and - ce2 = TCallable(getNodeEnclosingCallable(n), config) - ) - or - exists(Node n, Configuration config | - ce2 = TCallableSink() and - (config.isSink(n) or config.isSink(n, _)) and - ce1 = TCallable(getNodeEnclosingCallable(n), config) - ) - } - - private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { - callableExtStepFwd(ce2, ce1) - } - - private int distSrcExt(TCallableExt c) = - shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) - - private int distSinkExt(TCallableExt c) = - shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) - - private int distSrc(DataFlowCallable c, Configuration config) { - result = distSrcExt(TCallable(c, config)) - 1 - } - - private int distSink(DataFlowCallable c, Configuration config) { - result = distSinkExt(TCallable(c, config)) - 1 - } - - private newtype TPartialAccessPath = - TPartialNil(DataFlowType t) or - TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first - * element of the list and its length are tracked. If data flows from a source to - * a given node with a given `AccessPath`, this indicates the sequence of - * dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ - private class PartialAccessPath extends TPartialAccessPath { - abstract string toString(); - - TypedContent getHead() { this = TPartialCons(result, _) } - - int len() { - this = TPartialNil(_) and result = 0 - or - this = TPartialCons(_, result) - } - - DataFlowType getType() { - this = TPartialNil(result) - or - exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) - } - } - - private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { - override string toString() { - exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) - } - } - - private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { - override string toString() { - exists(TypedContent tc, int len | this = TPartialCons(tc, len) | - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private newtype TRevPartialAccessPath = - TRevPartialNil() or - TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `Content`s, but only the first - * element of the list and its length are tracked. - */ - private class RevPartialAccessPath extends TRevPartialAccessPath { - abstract string toString(); - - Content getHead() { this = TRevPartialCons(result, _) } - - int len() { - this = TRevPartialNil() and result = 0 - or - this = TRevPartialCons(_, result) - } - } - - private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { - override string toString() { result = "" } - } - - private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { - override string toString() { - exists(Content c, int len | this = TRevPartialCons(c, len) | - if len = 1 - then result = "[" + c.toString() + "]" - else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private predicate relevantState(FlowState state) { - sourceNode(_, state, _) or - sinkNode(_, state, _) or - additionalLocalStateStep(_, state, _, _, _) or - additionalLocalStateStep(_, _, _, state, _) or - additionalJumpStateStep(_, state, _, _, _) or - additionalJumpStateStep(_, _, _, state, _) - } - - private newtype TSummaryCtx1 = - TSummaryCtx1None() or - TSummaryCtx1Param(ParamNodeEx p) - - private newtype TSummaryCtx2 = - TSummaryCtx2None() or - TSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TSummaryCtx3 = - TSummaryCtx3None() or - TSummaryCtx3Some(PartialAccessPath ap) - - private newtype TRevSummaryCtx1 = - TRevSummaryCtx1None() or - TRevSummaryCtx1Some(ReturnPosition pos) - - private newtype TRevSummaryCtx2 = - TRevSummaryCtx2None() or - TRevSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TRevSummaryCtx3 = - TRevSummaryCtx3None() or - TRevSummaryCtx3Some(RevPartialAccessPath ap) - - private newtype TPartialPathNode = - TPartialPathNodeFwd( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = TPartialNil(node.getDataFlowType()) and - exists(config.explorationLimit()) - or - partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap, config) and - distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() - } or - TPartialPathNodeRev( - NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, - RevPartialAccessPath ap, Configuration config - ) { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() and - exists(config.explorationLimit()) - or - revPartialPathStep(_, node, state, sc1, sc2, sc3, ap, config) and - not clearsContentEx(node, ap.getHead()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead()) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() - } - - pragma[nomagic] - private predicate partialPathNodeMk0( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap, config) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - not clearsContentEx(node, ap.getHead().getContent()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead().getContent()) - ) and - if node.asNode() instanceof CastingNode - then compatibleTypes(node.getDataFlowType(), ap.getType()) - else any() - } - - /** - * A `Node` augmented with a call context, an access path, and a configuration. - */ - class PartialPathNode extends TPartialPathNode { - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() - } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - /** Gets the underlying `Node`. */ - final Node getNode() { this.getNodeEx().projectToNode() = result } - - FlowState getState() { none() } - - private NodeEx getNodeEx() { - result = this.(PartialPathNodeFwd).getNodeEx() or - result = this.(PartialPathNodeRev).getNodeEx() - } - - /** Gets the associated configuration. */ - Configuration getConfiguration() { none() } - - /** Gets a successor of this node, if any. */ - PartialPathNode getASuccessor() { none() } - - /** - * Gets the approximate distance to the nearest source measured in number - * of interprocedural steps. - */ - int getSourceDistance() { - result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - /** - * Gets the approximate distance to the nearest sink measured in number - * of interprocedural steps. - */ - int getSinkDistance() { - result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - private string ppAp() { - exists(string s | - s = this.(PartialPathNodeFwd).getAp().toString() or - s = this.(PartialPathNodeRev).getAp().toString() - | - if s = "" then result = "" else result = " " + s - ) - } - - private string ppCtx() { - result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" - } - - /** Holds if this is a source in a forward-flow path. */ - predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } - - /** Holds if this is a sink in a reverse-flow path. */ - predicate isRevSink() { this.(PartialPathNodeRev).isSink() } - } - - /** - * Provides the query predicates needed to include a graph in a path-problem query. - */ - module PartialPathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } - } - - private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { - NodeEx node; - FlowState state; - CallContext cc; - TSummaryCtx1 sc1; - TSummaryCtx2 sc2; - TSummaryCtx3 sc3; - PartialAccessPath ap; - Configuration config; - - PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - TSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TSummaryCtx3 getSummaryCtx3() { result = sc3 } - - PartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeFwd getASuccessor() { - partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp(), - result.getConfiguration()) - } - - predicate isSource() { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap instanceof TPartialNil - } - } - - private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { - NodeEx node; - FlowState state; - TRevSummaryCtx1 sc1; - TRevSummaryCtx2 sc2; - TRevSummaryCtx3 sc3; - RevPartialAccessPath ap; - Configuration config; - - PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } - - RevPartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeRev getASuccessor() { - revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), - this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp(), this.getConfiguration()) - } - - predicate isSink() { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() - } - } - - private predicate partialPathStep( - PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, - TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and - ( - localFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - ) - or - jumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - partialPathStoreStep(mid, _, _, node, ap) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(PartialAccessPath ap0, TypedContent tc | - partialPathReadStep(mid, ap0, tc, node, cc, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsFwd(ap, tc, ap0, config) - ) - or - partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap, config) - or - partialPathOutOfCallable(mid, node, state, cc, ap, config) and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() - or - partialPathThroughCallable(mid, node, state, cc, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - bindingset[result, i] - private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } - - pragma[inline] - private predicate partialPathStoreStep( - PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, - PartialAccessPath ap2 - ) { - exists(NodeEx midNode, DataFlowType contentType | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - store(midNode, tc, node, contentType, mid.getConfiguration()) and - ap2.getHead() = tc and - ap2.len() = unbindInt(ap1.len() + 1) and - compatibleTypes(ap1.getType(), contentType) - ) - } - - pragma[nomagic] - private predicate apConsFwd( - PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeFwd mid | - partialPathStoreStep(mid, ap1, tc, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate partialPathReadStep( - PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc, - Configuration config - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and - ap.getHead() = tc and - pragma[only_bind_into](config) = mid.getConfiguration() and - cc = mid.getCallContext() - ) - } - - private predicate partialPathOutOfCallable0( - PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, - PartialAccessPath ap, Configuration config - ) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - ap = mid.getAp() and - config = mid.getConfiguration() - } - - pragma[nomagic] - private predicate partialPathOutOfCallable1( - PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - partialPathOutOfCallable0(mid, pos, state, innercc, ap, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) - } - - private predicate partialPathOutOfCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(ReturnKindExt kind, DataFlowCall call | - partialPathOutOfCallable1(mid, call, kind, state, cc, ap, config) - | - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[noinline] - private predicate partialPathIntoArg( - PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ArgNode arg, ArgumentPosition apos | - arg = mid.getNodeEx().asNode() and - state = mid.getState() and - cc = mid.getCallContext() and - arg.argumentOf(call, apos) and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate partialPathIntoCallable0( - PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - partialPathIntoArg(mid, pos, state, outercc, call, ap, config) and - callable = resolveCall(call, outercc) - } - - private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, - CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ParameterPosition pos, DataFlowCallable callable | - partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - sc1 = TSummaryCtx1Param(p) and - sc2 = TSummaryCtx2Some(state) and - sc3 = TSummaryCtx3Some(ap) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) - } - - pragma[nomagic] - private predicate paramFlowsThroughInPartialPath( - ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeFwd mid, RetNodeEx ret | - mid.getNodeEx() = ret and - kind = ret.getKind() and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() and - ap = mid.getAp() - ) - } - - pragma[noinline] - private predicate partialPathThroughCallable0( - DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | - partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _, config) and - paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap, config) - ) - } - - private predicate partialPathThroughCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ReturnKindExt kind | - partialPathThroughCallable0(call, mid, kind, state, cc, ap, config) and - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[nomagic] - private predicate revPartialPathStep( - PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, - TRevSummaryCtx3 sc3, RevPartialAccessPath ap, Configuration config - ) { - localFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - jumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - revPartialPathReadStep(mid, _, _, node, ap) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(RevPartialAccessPath ap0, Content c | - revPartialPathStoreStep(mid, ap0, c, node, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsRev(ap, c, ap0, config) - ) - or - exists(ParamNodeEx p | - mid.getNodeEx() = p and - viableParamArgEx(_, p, node) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - or - exists(ReturnPosition pos | - revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap, config) and - pos = getReturnPosition(node.asNode()) - ) - or - revPartialPathThroughCallable(mid, node, state, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - pragma[inline] - private predicate revPartialPathReadStep( - PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, - RevPartialAccessPath ap2 - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - read(node, c, midNode, mid.getConfiguration()) and - ap2.getHead() = c and - ap2.len() = unbindInt(ap1.len() + 1) - ) - } - - pragma[nomagic] - private predicate apConsRev( - RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeRev mid | - revPartialPathReadStep(mid, ap1, c, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathStoreStep( - PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config - ) { - exists(NodeEx midNode, TypedContent tc | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - store(node, tc, midNode, _, config) and - ap.getHead() = c and - config = mid.getConfiguration() and - tc.getContent() = c - ) - } - - pragma[nomagic] - private predicate revPartialPathIntoReturn( - PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, - TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap, - Configuration config - ) { - exists(NodeEx out | - mid.getNodeEx() = out and - mid.getState() = state and - viableReturnPosOutEx(call, pos, out) and - sc1 = TRevSummaryCtx1Some(pos) and - sc2 = TRevSummaryCtx2Some(state) and - sc3 = TRevSummaryCtx3Some(ap) and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathFlowsThrough( - ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, - TRevSummaryCtx3Some sc3, RevPartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | - mid.getNodeEx() = p and - mid.getState() = state and - p.getPosition() = ppos and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable0( - DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, - RevPartialAccessPath ap, Configuration config - ) { - exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | - revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _, config) and - revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap, config) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ArgumentPosition pos | - revPartialPathThroughCallable0(call, mid, pos, state, ap, config) and - node.asNode().(ArgNode).argumentOf(call, pos) - ) - } -} - -import FlowExploration - -private predicate partialFlow( - PartialPathNode source, PartialPathNode node, Configuration configuration -) { - source.getConfiguration() = configuration and - source.isFwdSource() and - node = source.getASuccessor+() -} - -private predicate revPartialFlow( - PartialPathNode node, PartialPathNode sink, Configuration configuration -) { - sink.getConfiguration() = configuration and - sink.isRevSink() and - node.getASuccessor+() = sink -} +predicate flowsTo = hasFlow/3; diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImplCommon.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImplCommon.qll index 5d3becc8078..f8c56d41d8d 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImplCommon.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImplCommon.qll @@ -3,15 +3,18 @@ private import DataFlowImplSpecific::Public import Cached module DataFlowImplCommonPublic { - /** A state value to track during data flow. */ - class FlowState = string; + /** Provides `FlowState = string`. */ + module FlowStateString { + /** A state value to track during data flow. */ + class FlowState = string; - /** - * The default state, which is used when the state is unspecified for a source - * or a sink. - */ - class FlowStateEmpty extends FlowState { - FlowStateEmpty() { this = "" } + /** + * The default state, which is used when the state is unspecified for a source + * or a sink. + */ + class FlowStateEmpty extends FlowState { + FlowStateEmpty() { this = "" } + } } private newtype TFlowFeature = diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/PrintIRLocalFlow.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/PrintIRLocalFlow.qll index a14b2b00651..bbe236311fb 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/PrintIRLocalFlow.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/PrintIRLocalFlow.qll @@ -97,23 +97,23 @@ private string getNodeProperty(DataFlow::Node node, string key) { | kind, ", " ) - or - // Is there partial flow from a source to this node? - // This property will only be emitted if partial flow is enabled by overriding - // `DataFlow::Configuration::explorationLimit()`. - key = "pflow" and - result = - strictconcat(DataFlow::PartialPathNode sourceNode, DataFlow::PartialPathNode destNode, int dist, - int order1, int order2 | - any(DataFlow::Configuration cfg).hasPartialFlow(sourceNode, destNode, dist) and - destNode.getNode() = node and - // Only print flow from a source in the same function. - sourceNode.getNode().getEnclosingCallable() = node.getEnclosingCallable() - | - nodeId(sourceNode.getNode(), order1, order2) + "+" + dist.toString(), ", " - order by - order1, order2, dist desc - ) + // or + // // Is there partial flow from a source to this node? + // // This property will only be emitted if partial flow is enabled by overriding + // // `DataFlow::Configuration::explorationLimit()`. + // key = "pflow" and + // result = + // strictconcat(DataFlow::PartialPathNode sourceNode, DataFlow::PartialPathNode destNode, int dist, + // int order1, int order2 | + // any(DataFlow::Configuration cfg).hasPartialFlow(sourceNode, destNode, dist) and + // destNode.getNode() = node and + // // Only print flow from a source in the same function. + // sourceNode.getNode().getEnclosingCallable() = node.getEnclosingCallable() + // | + // nodeId(sourceNode.getNode(), order1, order2) + "+" + dist.toString(), ", " + // order by + // order1, order2, dist desc + // ) } /** diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/tainttracking1/TaintTracking.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/tainttracking1/TaintTracking.qll new file mode 100644 index 00000000000..7333264298e --- /dev/null +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/tainttracking1/TaintTracking.qll @@ -0,0 +1,63 @@ +/** + * Provides classes for performing local (intra-procedural) and + * global (inter-procedural) taint-tracking analyses. + */ + +import TaintTrackingParameter::Public +private import TaintTrackingParameter::Private + +private module AddTaintDefaults implements +DataFlowInternal::FullStateConfigSig { + import Config + + predicate isBarrier(DataFlow::Node node) { + Config::isBarrier(node) or defaultTaintSanitizer(node) + } + + predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + Config::isAdditionalFlowStep(node1, node2) or + defaultAdditionalTaintStep(node1, node2) + } + + predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) { + Config::allowImplicitRead(node, c) + or + ( + Config::isSink(node, _) or + Config::isAdditionalFlowStep(node, _) or + Config::isAdditionalFlowStep(node, _, _, _) + ) and + defaultImplicitTaintRead(node, c) + } +} + +/** + * Constructs a standard taint tracking computation. + */ +module Make implements DataFlow::DataFlowSig { + private module Config0 implements DataFlowInternal::FullStateConfigSig { + import DataFlowInternal::DefaultState + import Config + } + + private module C implements DataFlowInternal::FullStateConfigSig { + import AddTaintDefaults + } + + import DataFlowInternal::Impl +} + +/** + * Constructs a taint tracking computation using flow state. + */ +module MakeWithState implements DataFlow::DataFlowSig { + private module Config0 implements DataFlowInternal::FullStateConfigSig { + import Config + } + + private module C implements DataFlowInternal::FullStateConfigSig { + import AddTaintDefaults + } + + import DataFlowInternal::Impl +} diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/tainttracking1/TaintTrackingParameter.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/tainttracking1/TaintTrackingParameter.qll index 1a1d605bc74..19e10871a78 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/tainttracking1/TaintTrackingParameter.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/tainttracking1/TaintTrackingParameter.qll @@ -2,4 +2,5 @@ import semmle.code.cpp.ir.dataflow.internal.TaintTrackingUtil as Public module Private { import semmle.code.cpp.ir.dataflow.DataFlow::DataFlow as DataFlow + import semmle.code.cpp.ir.dataflow.internal.DataFlowImpl as DataFlowInternal } diff --git a/cpp/ql/src/Security/CWE/CWE-022/TaintedPath.ql b/cpp/ql/src/Security/CWE/CWE-022/TaintedPath.ql index d94241b749e..e66a717b846 100644 --- a/cpp/ql/src/Security/CWE/CWE-022/TaintedPath.ql +++ b/cpp/ql/src/Security/CWE/CWE-022/TaintedPath.ql @@ -19,7 +19,7 @@ import semmle.code.cpp.security.FunctionWithWrappers import semmle.code.cpp.security.FlowSources import semmle.code.cpp.ir.IR import semmle.code.cpp.ir.dataflow.TaintTracking -import DataFlow::PathGraph +import TaintedPath::PathGraph /** * A function for opening a file. @@ -70,18 +70,16 @@ predicate hasUpperBoundsCheck(Variable var) { ) } -class TaintedPathConfiguration extends TaintTracking::Configuration { - TaintedPathConfiguration() { this = "TaintedPathConfiguration" } +module TaintedPathConfiguration implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node node) { node instanceof FlowSource } - override predicate isSource(DataFlow::Node node) { node instanceof FlowSource } - - override predicate isSink(DataFlow::Node node) { + predicate isSink(DataFlow::Node node) { exists(FileFunction fileFunction | fileFunction.outermostWrapperFunctionCall(node.asIndirectArgument(), _) ) } - override predicate isSanitizer(DataFlow::Node node) { + predicate isBarrier(DataFlow::Node node) { node.asExpr().(Call).getTarget().getUnspecifiedType() instanceof ArithmeticType or exists(LoadInstruction load, Variable checkedVar | @@ -92,13 +90,15 @@ class TaintedPathConfiguration extends TaintTracking::Configuration { } } +module TaintedPath = TaintTracking::Make; + from - FileFunction fileFunction, Expr taintedArg, FlowSource taintSource, TaintedPathConfiguration cfg, - DataFlow::PathNode sourceNode, DataFlow::PathNode sinkNode, string callChain + FileFunction fileFunction, Expr taintedArg, FlowSource taintSource, + TaintedPath::PathNode sourceNode, TaintedPath::PathNode sinkNode, string callChain where taintedArg = sinkNode.getNode().asIndirectArgument() and fileFunction.outermostWrapperFunctionCall(taintedArg, callChain) and - cfg.hasFlowPath(sourceNode, sinkNode) and + TaintedPath::hasFlowPath(sourceNode, sinkNode) and taintSource = sourceNode.getNode() select taintedArg, sourceNode, sinkNode, "This argument to a file access function is derived from $@ and then passed to " + callChain + ".", diff --git a/cpp/ql/src/Security/CWE/CWE-078/ExecTainted.ql b/cpp/ql/src/Security/CWE/CWE-078/ExecTainted.ql index 53d534a50ad..9254f244760 100644 --- a/cpp/ql/src/Security/CWE/CWE-078/ExecTainted.ql +++ b/cpp/ql/src/Security/CWE/CWE-078/ExecTainted.ql @@ -22,7 +22,7 @@ import semmle.code.cpp.ir.dataflow.TaintTracking import semmle.code.cpp.ir.dataflow.TaintTracking2 import semmle.code.cpp.security.FlowSources import semmle.code.cpp.models.implementations.Strcat -import DataFlow::PathGraph +import ExecTaint::PathGraph /** * Holds if `incoming` is a string that is used in a format or concatenation function resulting @@ -55,29 +55,30 @@ predicate interestingConcatenation(DataFlow::Node incoming, DataFlow::Node outgo ) } -class ConcatState extends DataFlow::FlowState { - ConcatState() { this = "ConcatState" } +newtype TState = + TConcatState() or + TExecState(DataFlow::Node incoming, DataFlow::Node outgoing) { + interestingConcatenation(pragma[only_bind_into](incoming), pragma[only_bind_into](outgoing)) + } + +class ConcatState extends TConcatState { + string toString() { result = "ConcatState" } } -class ExecState extends DataFlow::FlowState { +class ExecState extends TExecState { DataFlow::Node incoming; DataFlow::Node outgoing; - ExecState() { - this = - "ExecState (" + incoming.getLocation() + " | " + incoming + ", " + outgoing.getLocation() + - " | " + outgoing + ")" and - interestingConcatenation(pragma[only_bind_into](incoming), pragma[only_bind_into](outgoing)) - } + ExecState() { this = TExecState(incoming, outgoing) } DataFlow::Node getIncomingNode() { result = incoming } DataFlow::Node getOutgoingNode() { result = outgoing } /** Holds if this is a possible `ExecState` for `sink`. */ - predicate isFeasibleForSink(DataFlow::Node sink) { - any(ExecStateConfiguration conf).hasFlow(outgoing, sink) - } + predicate isFeasibleForSink(DataFlow::Node sink) { ExecState::hasFlow(outgoing, sink) } + + string toString() { result = "ExecState" } } predicate isSinkImpl(DataFlow::Node sink, Expr command, string callChain) { @@ -85,7 +86,7 @@ predicate isSinkImpl(DataFlow::Node sink, Expr command, string callChain) { shellCommand(command, callChain) } -predicate isSanitizerImpl(DataFlow::Node node) { +predicate isBarrierImpl(DataFlow::Node node) { node.asExpr().getUnspecifiedType() instanceof IntegralType or node.asExpr().getUnspecifiedType() instanceof FloatingPointType @@ -96,56 +97,57 @@ predicate isSanitizerImpl(DataFlow::Node node) { * given sink. This avoids a cartesian product between all sinks and all `ExecState`s in * `ExecTaintConfiguration::isSink`. */ -class ExecStateConfiguration extends TaintTracking2::Configuration { - ExecStateConfiguration() { this = "ExecStateConfiguration" } +module ExecStateConfiguration implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { any(ExecState state).getOutgoingNode() = source } - override predicate isSource(DataFlow::Node source) { - any(ExecState state).getOutgoingNode() = source - } + predicate isSink(DataFlow::Node sink) { isSinkImpl(sink, _, _) } - override predicate isSink(DataFlow::Node sink) { isSinkImpl(sink, _, _) } + predicate isBarrier(DataFlow::Node node) { isBarrierImpl(node) } - override predicate isSanitizer(DataFlow::Node node) { isSanitizerImpl(node) } - - override predicate isSanitizerOut(DataFlow::Node node) { - isSink(node, _) // Prevent duplicates along a call chain, since `shellCommand` will include wrappers + predicate isBarrierOut(DataFlow::Node node) { + isSink(node) // Prevent duplicates along a call chain, since `shellCommand` will include wrappers } } -class ExecTaintConfiguration extends TaintTracking::Configuration { - ExecTaintConfiguration() { this = "ExecTaintConfiguration" } +module ExecState = TaintTracking::Make; - override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) { +module ExecTaintConfiguration implements DataFlow::StateConfigSig { + class FlowState = TState; + + predicate isSource(DataFlow::Node source, FlowState state) { source instanceof FlowSource and state instanceof ConcatState } - override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) { - any(ExecStateConfiguration conf).isSink(sink) and + predicate isSink(DataFlow::Node sink, FlowState state) { + ExecStateConfiguration::isSink(sink) and state.(ExecState).isFeasibleForSink(sink) } - override predicate isAdditionalTaintStep( - DataFlow::Node node1, DataFlow::FlowState state1, DataFlow::Node node2, - DataFlow::FlowState state2 + predicate isAdditionalFlowStep( + DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2 ) { state1 instanceof ConcatState and state2.(ExecState).getIncomingNode() = node1 and state2.(ExecState).getOutgoingNode() = node2 } - override predicate isSanitizer(DataFlow::Node node) { isSanitizerImpl(node) } + predicate isBarrier(DataFlow::Node node) { isBarrierImpl(node) } - override predicate isSanitizerOut(DataFlow::Node node) { + predicate isBarrier(DataFlow::Node node, FlowState state) { none() } + + predicate isBarrierOut(DataFlow::Node node) { isSink(node, _) // Prevent duplicates along a call chain, since `shellCommand` will include wrappers } } +module ExecTaint = TaintTracking::MakeWithState; + from - ExecTaintConfiguration conf, DataFlow::PathNode sourceNode, DataFlow::PathNode sinkNode, - string taintCause, string callChain, DataFlow::Node concatResult, Expr command + ExecTaint::PathNode sourceNode, ExecTaint::PathNode sinkNode, string taintCause, string callChain, + DataFlow::Node concatResult, Expr command where - conf.hasFlowPath(sourceNode, sinkNode) and + ExecTaint::hasFlowPath(sourceNode, sinkNode) and taintCause = sourceNode.getNode().(FlowSource).getSourceType() and isSinkImpl(sinkNode.getNode(), command, callChain) and concatResult = sinkNode.getState().(ExecState).getOutgoingNode() diff --git a/cpp/ql/src/Security/CWE/CWE-190/TaintedAllocationSize.ql b/cpp/ql/src/Security/CWE/CWE-190/TaintedAllocationSize.ql index f05b0408164..45e26985256 100644 --- a/cpp/ql/src/Security/CWE/CWE-190/TaintedAllocationSize.ql +++ b/cpp/ql/src/Security/CWE/CWE-190/TaintedAllocationSize.ql @@ -19,7 +19,7 @@ import semmle.code.cpp.ir.dataflow.TaintTracking import semmle.code.cpp.ir.IR import semmle.code.cpp.controlflow.IRGuards import semmle.code.cpp.security.FlowSources -import DataFlow::PathGraph +import TaintedAllocationSize::PathGraph /** * Holds if `alloc` is an allocation, and `tainted` is a child of it that is a @@ -54,14 +54,12 @@ predicate nodeIsBarrierEqualityCandidate(DataFlow::Node node, Operand access, Va predicate isFlowSource(FlowSource source, string sourceType) { sourceType = source.getSourceType() } -class TaintedAllocationSizeConfiguration extends TaintTracking::Configuration { - TaintedAllocationSizeConfiguration() { this = "TaintedAllocationSizeConfiguration" } +module TaintedAllocationSizeConfiguration implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { isFlowSource(source, _) } - override predicate isSource(DataFlow::Node source) { isFlowSource(source, _) } + predicate isSink(DataFlow::Node sink) { allocSink(_, sink) } - override predicate isSink(DataFlow::Node sink) { allocSink(_, sink) } - - override predicate isSanitizer(DataFlow::Node node) { + predicate isBarrier(DataFlow::Node node) { exists(Expr e | e = node.asExpr() | // There can be two separate reasons for `convertedExprMightOverflow` not holding: // 1. `e` really cannot overflow. @@ -97,12 +95,14 @@ class TaintedAllocationSizeConfiguration extends TaintTracking::Configuration { } } +module TaintedAllocationSize = TaintTracking::Make; + from - Expr alloc, DataFlow::PathNode source, DataFlow::PathNode sink, string taintCause, - TaintedAllocationSizeConfiguration conf + Expr alloc, TaintedAllocationSize::PathNode source, TaintedAllocationSize::PathNode sink, + string taintCause where isFlowSource(source.getNode(), taintCause) and - conf.hasFlowPath(source, sink) and + TaintedAllocationSize::hasFlowPath(source, sink) and allocSink(alloc, sink.getNode()) select alloc, source, sink, "This allocation size is derived from $@ and might overflow.", source.getNode(), "user input (" + taintCause + ")" diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-369/DivideByZeroUsingReturnValue.cpp b/cpp/ql/src/experimental/Security/CWE/CWE-369/DivideByZeroUsingReturnValue.cpp new file mode 100644 index 00000000000..1d970a47678 --- /dev/null +++ b/cpp/ql/src/experimental/Security/CWE/CWE-369/DivideByZeroUsingReturnValue.cpp @@ -0,0 +1,7 @@ + +... + a = getc(f); + if (a < 123) ret = 123/a; // BAD +... + if (a != 0) ret = 123/a; // GOOD +... diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-369/DivideByZeroUsingReturnValue.qhelp b/cpp/ql/src/experimental/Security/CWE/CWE-369/DivideByZeroUsingReturnValue.qhelp new file mode 100644 index 00000000000..39ae034f46c --- /dev/null +++ b/cpp/ql/src/experimental/Security/CWE/CWE-369/DivideByZeroUsingReturnValue.qhelp @@ -0,0 +1,23 @@ + + + +

Possible cases of division by zero when using the return value from functions.

+ +
+ + +

The following example shows the use of a function with an error when using the return value and without an error.

+ + +
+ + +
  • + CERT Coding Standard: + INT33-C. Ensure that division and remainder operations do not result in divide-by-zero errors - SEI CERT C Coding Standard - Confluence. +
  • + +
    +
    \ No newline at end of file diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-369/DivideByZeroUsingReturnValue.ql b/cpp/ql/src/experimental/Security/CWE/CWE-369/DivideByZeroUsingReturnValue.ql new file mode 100644 index 00000000000..d55743c592b --- /dev/null +++ b/cpp/ql/src/experimental/Security/CWE/CWE-369/DivideByZeroUsingReturnValue.ql @@ -0,0 +1,274 @@ +/** + * @name Divide by zero using return value + * @description Possible cases of division by zero when using the return value from functions. + * @kind problem + * @id cpp/divide-by-zero-using-return-value + * @problem.severity warning + * @precision medium + * @tags correctness + * security + * external/cwe/cwe-369 + */ + +import cpp +import semmle.code.cpp.valuenumbering.GlobalValueNumbering +import semmle.code.cpp.controlflow.Guards + +/** Holds if function `fn` can return a value equal to value `val` */ +predicate mayBeReturnValue(Function fn, float val) { + exists(Expr tmpExp, ReturnStmt rs | + tmpExp.getValue().toFloat() = val and + rs.getEnclosingFunction() = fn and + ( + globalValueNumber(rs.getExpr()) = globalValueNumber(tmpExp) + or + exists(AssignExpr ae | + ae.getLValue().(VariableAccess).getTarget() = + globalValueNumber(rs.getExpr()).getAnExpr().(VariableAccess).getTarget() and + globalValueNumber(ae.getRValue()) = globalValueNumber(tmpExp) + ) + or + exists(Initializer it | + globalValueNumber(it.getExpr()) = globalValueNumber(tmpExp) and + it.getDeclaration().(Variable).getAnAccess().getTarget() = + globalValueNumber(rs.getExpr()).getAnExpr().(VariableAccess).getTarget() + ) + ) + ) +} + +/** Holds if function `fn` can return a value equal zero */ +predicate mayBeReturnZero(Function fn) { + mayBeReturnValue(fn, 0) + or + fn.hasName([ + "iswalpha", "iswlower", "iswprint", "iswspace", "iswblank", "iswupper", "iswcntrl", + "iswctype", "iswalnum", "iswgraph", "iswxdigit", "iswdigit", "iswpunct", "isblank", "isupper", + "isgraph", "isalnum", "ispunct", "islower", "isspace", "isprint", "isxdigit", "iscntrl", + "isdigit", "isalpha", "timespec_get", "feof", "atomic_is_lock_free", + "atomic_compare_exchange", "thrd_equal", "isfinite", "islessequal", "isnan", "isgreater", + "signbit", "isinf", "islessgreater", "isnormal", "isless", "isgreaterequal", "isunordered", + "ferror" + ]) + or + fn.hasName([ + "thrd_sleep", "feenv", "feholdexcept", "feclearexcept", "feexceptflag", "feupdateenv", + "remove", "fflush", "setvbuf", "fgetpos", "fsetpos", "fclose", "rename", "fseek", "raise" + ]) + or + fn.hasName(["tss_get", "gets"]) + or + fn.hasName(["getc", "atoi"]) +} + +/** Gets the Guard which compares the expression `bound` */ +pragma[inline] +GuardCondition checkByValue(Expr bound, Expr val) { + exists(GuardCondition gc | + ( + gc.ensuresEq(bound, val, _, _, _) or + gc.ensuresEq(val, bound, _, _, _) or + gc.ensuresLt(bound, val, _, _, _) or + gc.ensuresLt(val, bound, _, _, _) or + gc = globalValueNumber(bound).getAnExpr() + ) and + result = gc + ) +} + +/** Holds if there are no comparisons between the value returned by possible function calls `compArg` and the value `valArg`, or when these comparisons do not exclude equality to the value `valArg`. */ +pragma[inline] +predicate compareFunctionWithValue(Expr guardExp, Function compArg, Expr valArg) { + not exists(Expr exp | + exp.getAChild*() = globalValueNumber(compArg.getACallToThisFunction()).getAnExpr() and + checkByValue(exp, valArg).controls(guardExp.getBasicBlock(), _) + ) + or + exists(GuardCondition gc | + ( + gc.ensuresEq(globalValueNumber(compArg.getACallToThisFunction()).getAnExpr(), valArg, 0, + guardExp.getBasicBlock(), true) + or + gc.ensuresEq(valArg, globalValueNumber(compArg.getACallToThisFunction()).getAnExpr(), 0, + guardExp.getBasicBlock(), true) + or + gc.ensuresLt(globalValueNumber(compArg.getACallToThisFunction()).getAnExpr(), valArg, 0, + guardExp.getBasicBlock(), false) + or + gc.ensuresLt(valArg, globalValueNumber(compArg.getACallToThisFunction()).getAnExpr(), 0, + guardExp.getBasicBlock(), false) + ) + or + exists(Expr exp | + exp.getValue().toFloat() > valArg.getValue().toFloat() and + gc.ensuresLt(globalValueNumber(compArg.getACallToThisFunction()).getAnExpr(), exp, 0, + guardExp.getBasicBlock(), true) + or + exp.getValue().toFloat() < valArg.getValue().toFloat() and + gc.ensuresLt(exp, globalValueNumber(compArg.getACallToThisFunction()).getAnExpr(), 0, + guardExp.getBasicBlock(), true) + ) + ) + or + valArg.getValue().toFloat() = 0 and + exists(NotExpr ne, IfStmt ifne | + ne.getOperand() = globalValueNumber(compArg.getACallToThisFunction()).getAnExpr() and + ifne.getCondition() = ne and + ifne.getThen().getAChild*() = guardExp + ) +} + +/** Wraping predicate for call `compareFunctionWithValue`. */ +pragma[inline] +predicate checkConditions1(Expr div, Function fn, float changeInt) { + exists(Expr val | + val.getEnclosingFunction() = fn and + val.getValue().toFloat() = changeInt and + compareFunctionWithValue(div, fn, val) + ) +} + +/** Holds if there are no comparisons between the value `compArg` and the value `valArg`, or when these comparisons do not exclude equality to the value `valArg`. */ +pragma[inline] +predicate compareExprWithValue(Expr guardExp, Expr compArg, Expr valArg) { + not exists(Expr exp | + exp.getAChild*() = globalValueNumber(compArg).getAnExpr() and + checkByValue(exp, valArg).controls(guardExp.getBasicBlock(), _) + ) + or + exists(GuardCondition gc | + ( + gc.ensuresEq(globalValueNumber(compArg).getAnExpr(), valArg, 0, guardExp.getBasicBlock(), true) + or + gc.ensuresEq(valArg, globalValueNumber(compArg).getAnExpr(), 0, guardExp.getBasicBlock(), true) + or + gc.ensuresLt(globalValueNumber(compArg).getAnExpr(), valArg, 0, guardExp.getBasicBlock(), + false) + or + gc.ensuresLt(valArg, globalValueNumber(compArg).getAnExpr(), 0, guardExp.getBasicBlock(), + false) + ) + or + exists(Expr exp | + exp.getValue().toFloat() > valArg.getValue().toFloat() and + gc.ensuresLt(globalValueNumber(compArg).getAnExpr(), exp, 0, guardExp.getBasicBlock(), true) + or + exp.getValue().toFloat() < valArg.getValue().toFloat() and + gc.ensuresLt(exp, globalValueNumber(compArg).getAnExpr(), 0, guardExp.getBasicBlock(), true) + ) + ) + or + valArg.getValue().toFloat() = 0 and + exists(NotExpr ne, IfStmt ifne | + ne.getOperand() = globalValueNumber(compArg).getAnExpr() and + ifne.getCondition() = ne and + ifne.getThen().getAChild*() = guardExp + ) +} + +/** Wraping predicate for call `compareExprWithValue`. */ +pragma[inline] +predicate checkConditions2(Expr div, Expr divVal, float changeInt2) { + exists(Expr val | + ( + val.getEnclosingFunction() = + div.getEnclosingFunction().getACallToThisFunction().getEnclosingFunction() or + val.getEnclosingFunction() = div.getEnclosingFunction() + ) and + val.getValue().toFloat() = changeInt2 and + compareExprWithValue(div, divVal, val) + ) +} + +/** Gets the value of the difference or summand from the expression `src`. */ +float getValueOperand(Expr src, Expr e1, Expr e2) { + src.(SubExpr).hasOperands(e1, e2) and + result = e2.getValue().toFloat() + or + src.(AddExpr).hasOperands(e1, e2) and + result = -e2.getValue().toFloat() +} + +/** Function the return of the expression `e1` and the multiplication operands, or the left operand of division if `e1` contains a multiplication or division, respectively. */ +Expr getMulDivOperand(Expr e1) { + result = e1 or + result = e1.(MulExpr).getAnOperand() or + result = e1.(DivExpr).getLeftOperand() +} + +/** The class that defines possible variants of the division expression or the search for the remainder. */ +class MyDiv extends Expr { + MyDiv() { + this instanceof DivExpr or + this instanceof RemExpr or + this instanceof AssignDivExpr or + this instanceof AssignRemExpr + } + + Expr getRV() { + result = this.(AssignArithmeticOperation).getRValue() or + result = this.(BinaryArithmeticOperation).getRightOperand() + } +} + +from Expr exp, string msg, Function fn, GVN findVal, float changeInt, MyDiv div +where + findVal = globalValueNumber(fn.getACallToThisFunction()) and + ( + // Look for divide-by-zero operations possible due to the return value of the function `fn`. + checkConditions1(div, fn, changeInt) and + ( + // Function return value can be zero. + mayBeReturnZero(fn) and + getMulDivOperand(globalValueNumber(div.getRV()).getAnExpr()) = findVal.getAnExpr() and + changeInt = 0 + or + // Denominator can be sum or difference. + changeInt = getValueOperand(div.getRV(), findVal.getAnExpr(), _) and + mayBeReturnValue(fn, changeInt) + ) and + exp = div and + msg = + "Can lead to division by 0, since the function " + fn.getName() + " can return a value " + + changeInt.toString() + "." + or + // Search for situations where division by zero is possible inside the `divFn` function if the passed argument can be equal to a certain value. + exists(int posArg, Expr divVal, FunctionCall divFc, float changeInt2 | + // Division is associated with the function argument. + exists(Function divFn | + divFn.getParameter(posArg).getAnAccess() = divVal and + divVal.getEnclosingStmt() = div.getEnclosingStmt() and + divFc = divFn.getACallToThisFunction() + ) and + ( + divVal = div.getRV() and + divFc.getArgument(posArg) != findVal.getAnExpr() and + ( + // Function return value can be zero. + mayBeReturnZero(fn) and + getMulDivOperand(globalValueNumber(divFc.getArgument(posArg)).getAnExpr()) = + findVal.getAnExpr() and + changeInt = 0 and + changeInt2 = 0 + or + // Denominator can be sum or difference. + changeInt = getValueOperand(divFc.getArgument(posArg), findVal.getAnExpr(), _) and + mayBeReturnValue(fn, changeInt) and + changeInt2 = 0 + ) + or + // Look for a situation where the difference or subtraction is considered as an argument, and it can be used in the same way. + changeInt = getValueOperand(div.getRV(), divVal, _) and + changeInt2 = changeInt and + mayBeReturnValue(fn, changeInt) and + divFc.getArgument(posArg) = findVal.getAnExpr() + ) and + checkConditions2(div, divVal, changeInt2) and + checkConditions1(divFc, fn, changeInt) and + exp = divFc and + msg = + "Can lead to division by 0, since the function " + fn.getName() + " can return a value " + + changeInt.toString() + "." + ) + ) +select exp, msg diff --git a/cpp/ql/test/examples/docs-examples/analyzing-data-flow-in-cpp/exercise4.ql b/cpp/ql/test/examples/docs-examples/analyzing-data-flow-in-cpp/exercise4.ql index 993265105d5..09c01b953e3 100644 --- a/cpp/ql/test/examples/docs-examples/analyzing-data-flow-in-cpp/exercise4.ql +++ b/cpp/ql/test/examples/docs-examples/analyzing-data-flow-in-cpp/exercise4.ql @@ -2,7 +2,7 @@ import cpp import semmle.code.cpp.dataflow.new.DataFlow class GetenvSource extends DataFlow::Node { - GetenvSource() { this.asIndirectExpr(1).(FunctionCall).getTarget().hasQualifiedName("getenv") } + GetenvSource() { this.asIndirectExpr(1).(FunctionCall).getTarget().hasGlobalName("getenv") } } class GetenvToGethostbynameConfiguration extends DataFlow::Configuration { diff --git a/cpp/ql/test/examples/docs-examples/analyzing-data-flow-in-cpp/fopen-flow-from-expr.ql b/cpp/ql/test/examples/docs-examples/analyzing-data-flow-in-cpp/fopen-flow-from-expr.ql index d85f68e27b9..9f552574972 100644 --- a/cpp/ql/test/examples/docs-examples/analyzing-data-flow-in-cpp/fopen-flow-from-expr.ql +++ b/cpp/ql/test/examples/docs-examples/analyzing-data-flow-in-cpp/fopen-flow-from-expr.ql @@ -3,7 +3,7 @@ import semmle.code.cpp.dataflow.new.DataFlow from Function fopen, FunctionCall fc, Expr src, DataFlow::Node source, DataFlow::Node sink where - fopen.hasQualifiedName("fopen") and + fopen.hasGlobalName("fopen") and fc.getTarget() = fopen and source.asIndirectExpr(1) = src and sink.asIndirectExpr(1) = fc.getArgument(0) and diff --git a/cpp/ql/test/examples/docs-examples/analyzing-data-flow-in-cpp/fopen-flow-from-getenv.ql b/cpp/ql/test/examples/docs-examples/analyzing-data-flow-in-cpp/fopen-flow-from-getenv.ql index 34038df7a65..d21ae4ae52e 100644 --- a/cpp/ql/test/examples/docs-examples/analyzing-data-flow-in-cpp/fopen-flow-from-getenv.ql +++ b/cpp/ql/test/examples/docs-examples/analyzing-data-flow-in-cpp/fopen-flow-from-getenv.ql @@ -7,14 +7,14 @@ class EnvironmentToFileConfiguration extends DataFlow::Configuration { override predicate isSource(DataFlow::Node source) { exists(Function getenv | source.asIndirectExpr(1).(FunctionCall).getTarget() = getenv and - getenv.hasQualifiedName("getenv") + getenv.hasGlobalName("getenv") ) } override predicate isSink(DataFlow::Node sink) { exists(FunctionCall fc | sink.asIndirectExpr(1) = fc.getArgument(0) and - fc.getTarget().hasQualifiedName("fopen") + fc.getTarget().hasGlobalName("fopen") ) } } diff --git a/cpp/ql/test/examples/docs-examples/analyzing-data-flow-in-cpp/fopen-flow-from-param.ql b/cpp/ql/test/examples/docs-examples/analyzing-data-flow-in-cpp/fopen-flow-from-param.ql index d24d65cd288..423645ae0a9 100644 --- a/cpp/ql/test/examples/docs-examples/analyzing-data-flow-in-cpp/fopen-flow-from-param.ql +++ b/cpp/ql/test/examples/docs-examples/analyzing-data-flow-in-cpp/fopen-flow-from-param.ql @@ -3,7 +3,7 @@ import semmle.code.cpp.dataflow.new.DataFlow from Function fopen, FunctionCall fc, Parameter p, DataFlow::Node source, DataFlow::Node sink where - fopen.hasQualifiedName("fopen") and + fopen.hasGlobalName("fopen") and fc.getTarget() = fopen and source.asParameter(1) = p and sink.asIndirectExpr(1) = fc.getArgument(0) and diff --git a/cpp/ql/test/examples/docs-examples/analyzing-data-flow-in-cpp/fopen-no-flow.ql b/cpp/ql/test/examples/docs-examples/analyzing-data-flow-in-cpp/fopen-no-flow.ql index 0bcacf403d8..48c124352b7 100644 --- a/cpp/ql/test/examples/docs-examples/analyzing-data-flow-in-cpp/fopen-no-flow.ql +++ b/cpp/ql/test/examples/docs-examples/analyzing-data-flow-in-cpp/fopen-no-flow.ql @@ -2,6 +2,6 @@ import cpp from Function fopen, FunctionCall fc where - fopen.hasQualifiedName("fopen") and + fopen.hasGlobalName("fopen") and fc.getTarget() = fopen select fc.getArgument(0) diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-369/semmle/tests/DivideByZeroUsingReturnValue.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-369/semmle/tests/DivideByZeroUsingReturnValue.expected new file mode 100644 index 00000000000..b8296505cb8 --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-369/semmle/tests/DivideByZeroUsingReturnValue.expected @@ -0,0 +1,27 @@ +| test.cpp:47:24:47:31 | ... / ... | Can lead to division by 0, since the function getSize can return a value 0. | +| test.cpp:48:15:48:34 | ... / ... | Can lead to division by 0, since the function getSize2 can return a value 0. | +| test.cpp:53:10:53:17 | ... / ... | Can lead to division by 0, since the function getSize can return a value 0. | +| test.cpp:65:15:65:22 | ... / ... | Can lead to division by 0, since the function getSize can return a value 0. | +| test.cpp:68:15:68:22 | ... / ... | Can lead to division by 0, since the function getSize can return a value 0. | +| test.cpp:71:9:71:16 | ... / ... | Can lead to division by 0, since the function getSize can return a value 0. | +| test.cpp:74:9:74:16 | ... / ... | Can lead to division by 0, since the function getSize can return a value 0. | +| test.cpp:77:21:77:28 | ... / ... | Can lead to division by 0, since the function getSize can return a value 0. | +| test.cpp:79:25:79:32 | ... / ... | Can lead to division by 0, since the function getSize can return a value 0. | +| test.cpp:81:24:81:31 | ... / ... | Can lead to division by 0, since the function getSize can return a value 0. | +| test.cpp:128:10:128:16 | ... / ... | Can lead to division by 0, since the function getSize can return a value 0. | +| test.cpp:135:10:135:16 | ... / ... | Can lead to division by 0, since the function getSize can return a value 0. | +| test.cpp:141:10:141:23 | ... / ... | Can lead to division by 0, since the function getSize can return a value 0. | +| test.cpp:153:12:153:19 | ... / ... | Can lead to division by 0, since the function getSize can return a value 0. | +| test.cpp:172:3:172:12 | ... /= ... | Can lead to division by 0, since the function getSize can return a value 0. | +| test.cpp:173:3:173:12 | ... %= ... | Can lead to division by 0, since the function getSize can return a value 0. | +| test.cpp:187:10:187:17 | ... / ... | Can lead to division by 0, since the function getSizeFloat can return a value 0. | +| test.cpp:199:12:199:25 | ... / ... | Can lead to division by 0, since the function getSize can return a value -1. | +| test.cpp:202:12:202:25 | ... / ... | Can lead to division by 0, since the function getSize can return a value 1. | +| test.cpp:205:10:205:23 | ... / ... | Can lead to division by 0, since the function getSize can return a value 1. | +| test.cpp:210:10:210:23 | ... / ... | Can lead to division by 0, since the function getSize can return a value 3. | +| test.cpp:258:3:258:10 | call to badMyDiv | Can lead to division by 0, since the function getSize can return a value 0. | +| test.cpp:259:3:259:10 | call to badMyDiv | Can lead to division by 0, since the function getSize can return a value 2. | +| test.cpp:260:3:260:13 | call to badMySubDiv | Can lead to division by 0, since the function getSize can return a value 3. | +| test.cpp:263:5:263:15 | call to badMySubDiv | Can lead to division by 0, since the function getSize can return a value 3. | +| test.cpp:273:5:273:12 | call to badMyDiv | Can lead to division by 0, since the function getSize can return a value 3. | +| test.cpp:275:5:275:12 | call to badMyDiv | Can lead to division by 0, since the function getSize can return a value -1. | diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-369/semmle/tests/DivideByZeroUsingReturnValue.qlref b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-369/semmle/tests/DivideByZeroUsingReturnValue.qlref new file mode 100644 index 00000000000..e134a5229da --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-369/semmle/tests/DivideByZeroUsingReturnValue.qlref @@ -0,0 +1 @@ +experimental/Security/CWE/CWE-369/DivideByZeroUsingReturnValue.ql diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-369/semmle/tests/test.cpp b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-369/semmle/tests/test.cpp new file mode 100644 index 00000000000..882f6618485 --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-369/semmle/tests/test.cpp @@ -0,0 +1,278 @@ +typedef struct {} +FILE; +int getc(FILE * stream); + +int getSize(int type) { + int st; + switch (type) { + case 1: + st = 1; + break; + case 2: + st = 2; + break; + case 3: + st = 3; + break; + case 4: + st = -1; + break; + default: + st = 0; + break; + } + return st; +} +int getSize2(int type) { + int st = 0; + switch (type) { + case 1: + st = 1; + break; + case 2: + st = 2; + break; + case 3: + st = 3; + break; + case 4: + st = -1; + break; + } + return st; +} + +int badTestf1(int type, int met) { + int is = getSize(type); + if (met == 1) return 123 / is; // BAD + else return 123 / getSize2(type); // BAD +} +int badTestf2(int type) { + int is; + is = getSize(type); + return 123 / is; // BAD +} + +int badTestf3(int type, int met) { + int is; + is = getSize(type); + switch (met) { + case 1: + if (is >= 0) return 123 / is; // BAD [NOT DETECTED] + case 2: + if (0 == is) return 123 / is; // BAD [NOT DETECTED] + case 3: + if (!is & 123 / is) // BAD + return 123; + case 4: + if (!is | 123 / is) // BAD + return 123; + case 5: + if (123 / is || !is) // BAD + return 123; + case 6: + if (123 / is && !is) // BAD + return 123; + case 7: + if (!is) return 123 / is; // BAD + case 8: + if (is > -1) return 123 / is; // BAD + case 9: + if (is < 2) return 123 / is; // BAD + } + if (is != 0) return -1; + if (is == 0) type += 1; + return 123 / is; // BAD [NOT DETECTED] +} + +int goodTestf3(int type, int met) { + int is = getSize(type); + if (is == 0) return -1; + switch (met) { + case 1: + if (is < 0) return 123 / is; // GOOD + case 2: + if (!is && 123 / is) // GOOD + return 123; + case 3: + if (!is || 123 / is) // GOOD + return 123; + case 8: + if (is < -1) return 123 / is; // GOOD + case 9: + if (is > 2) return 123 / is; // GOOD + } + return 123 / is; +} + +int goodTestf3a(int type, int met) { + int is = getSize(type); + switch (met) { + case 1: + if (is < 0) + return 123 / is; // GOOD + case 2: + if (!is && 123 / is) // GOOD + return 123; + case 3: + if (!is || 123 / is) // GOOD + return 123; + } + return 1; +} + +int badTestf4(int type) { + int is = getSize(type); + int d; + d = type * is; + return 123 / d; // BAD +} + +int badTestf5(int type) { + int is = getSize(type); + int d; + d = is / type; + return 123 / d; // BAD +} +int badTestf6(int type) { + int is = getSize(type); + int d; + d = is / type; + return type * 123 / d; // BAD +} + +int badTestf7(int type, int met) { + int is = getSize(type); + if (is == 0) goto quit; + switch (met) { + case 1: + if (is < 0) + return 123 / is; // GOOD + } + quit: + return 123 / is; // BAD +} + +int goodTestf7(int type, int met) { + int is = getSize(type); + if (is == 0) goto quit2; + if (is == 0.) return -1; + switch (met) { + case 1: + if (is < 0.) + return 123 / is; // GOOD + } + return 123 / is; // GOOD + quit2: + return -1; +} + +int badTestf8(int type) { + int is = getSize(type); + type /= is; // BAD + type %= is; // BAD + return type; +} + +float getSizeFloat(float type) { + float st; + if (type) + st = 1.0; + else + st = 0.0; + return st; +} +float badTestf9(float type) { + float is = getSizeFloat(type); + return 123 / is; // BAD +} +float goodTestf9(float type) { + float is = getSizeFloat(type); + if (is == 0.0) return -1; + return 123 / is; // GOOD +} + +int badTestf10(int type) { + int out = type; + int is = getSize(type); + if (is > -2) { + out /= 123 / (is + 1); // BAD + } + if (is > 0) { + return 123 / (is - 1); // BAD + } + if (is <= 0) return 0; + return 123 / (is - 1); // BAD + return 0; +} +int badTestf11(int type) { + int is = getSize(type); + return 123 / (is - 3); // BAD +} + +int goodTestf11(int type) { + int is = getSize(type); + if (is > 1) { + return 123 / (is - 1); // GOOD + } else { + return 0; + } +} + +int badTestf12(FILE * f) { + int a; + int ret = -1; + a = getc(f); + if (a == 0) ret = 123 / a; // BAD [NOT DETECTED] + return ret; +} + +int goodTestf12(FILE * f) { + int a; + int ret = -1; + a = getc(f); + if (a != 0) ret = 123 / a; // GOOD + return ret; +} + +int badMyDiv(int type, int is) { + type /= is; + type %= is; + return type; +} + +int goodMyDiv(int type, int is) { + if (is == 0) return -1; + type /= is; + type %= is; + return type; +} +int badMySubDiv(int type, int is) { + type /= (is - 3); + type %= (is + 1); + return type; +} + +void badTestf13(int type) { + int is = getSize(type); + badMyDiv(type, is); // BAD + badMyDiv(type, is - 2); // BAD + badMySubDiv(type, is); // BAD + goodMyDiv(type, is); // GOOD + if (is < 5) + badMySubDiv(type, is); // BAD + if (is < 0) + badMySubDiv(type, is); // BAD [NOT DETECTED] + if (is > 5) + badMySubDiv(type, is); // GOOD + if (is == 0) + badMyDiv(type, is); // BAD + if (is > 0) + badMyDiv(type, is); // GOOD + if (is < 5) + badMyDiv(type, is - 3); // BAD + if (is < 0) + badMyDiv(type, is + 1); // BAD + if (is > 5) + badMyDiv(type, is - 3); // GOOD +} diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-078/semmle/ExecTainted/ExecTainted.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-078/semmle/ExecTainted/ExecTainted.expected index 71d5edfd78e..516fae951c9 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-078/semmle/ExecTainted/ExecTainted.expected +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-078/semmle/ExecTainted/ExecTainted.expected @@ -60,8 +60,6 @@ edges | test.cpp:220:10:220:16 | strncat output argument | test.cpp:222:32:222:38 | command indirection | | test.cpp:220:19:220:26 | filename indirection | test.cpp:220:10:220:16 | strncat output argument | | test.cpp:220:19:220:26 | filename indirection | test.cpp:220:10:220:16 | strncat output argument | -| test.cpp:220:19:220:26 | filename indirection | test.cpp:220:10:220:16 | strncat output argument | -| test.cpp:220:19:220:26 | filename indirection | test.cpp:220:10:220:16 | strncat output argument | nodes | test.cpp:15:27:15:30 | argv indirection | semmle.label | argv indirection | | test.cpp:15:27:15:30 | argv indirection | semmle.label | argv indirection | @@ -133,6 +131,7 @@ nodes | test.cpp:220:19:220:26 | filename indirection | semmle.label | filename indirection | | test.cpp:220:19:220:26 | filename indirection | semmle.label | filename indirection | | test.cpp:222:32:222:38 | command indirection | semmle.label | command indirection | +| test.cpp:222:32:222:38 | command indirection | semmle.label | command indirection | subpaths | test.cpp:196:26:196:33 | filename indirection | test.cpp:186:47:186:54 | filename indirection | test.cpp:188:11:188:17 | strncat output argument | test.cpp:196:10:196:16 | concat output argument | | test.cpp:196:26:196:33 | filename indirection | test.cpp:186:47:186:54 | filename indirection | test.cpp:188:11:188:17 | strncat output argument | test.cpp:196:10:196:16 | concat output argument | diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Conversion.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Conversion.cs index c3d59f94de7..c081f43e24f 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Conversion.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Conversion.cs @@ -10,6 +10,8 @@ namespace Semmle.Extraction.CSharp.Entities private Conversion(Context cx, IMethodSymbol init) : base(cx, init) { } + protected override MethodKind ExplicitlyImplementsKind => MethodKind.Conversion; + public static new Conversion Create(Context cx, IMethodSymbol symbol) => ConversionFactory.Instance.CreateEntityFromSymbol(cx, symbol); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Method.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Method.cs index cd765159769..22bf9f69670 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Method.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Method.cs @@ -83,10 +83,12 @@ namespace Semmle.Extraction.CSharp.Entities } } + protected virtual MethodKind ExplicitlyImplementsKind => MethodKind.Ordinary; + public void Overrides(TextWriter trapFile) { foreach (var explicitInterface in Symbol.ExplicitInterfaceImplementations - .Where(sym => sym.MethodKind == MethodKind.Ordinary) + .Where(sym => sym.MethodKind == ExplicitlyImplementsKind) .Select(impl => Type.Create(Context, impl.ContainingType))) { trapFile.explicitly_implements(this, explicitInterface.TypeRef); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Modifier.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Modifier.cs index 15da81bf5e4..145b1ffabae 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Modifier.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Modifier.cs @@ -85,6 +85,9 @@ namespace Semmle.Extraction.CSharp.Entities if (nt.IsRecord) HasModifier(cx, trapFile, key, Modifiers.Record); + if (nt.IsFileLocal) + HasModifier(cx, trapFile, key, Modifiers.File); + if (nt.TypeKind == TypeKind.Struct) { if (nt.IsReadOnly) @@ -97,7 +100,11 @@ namespace Semmle.Extraction.CSharp.Entities public static void ExtractModifiers(Context cx, TextWriter trapFile, IEntity key, ISymbol symbol) { - HasAccessibility(cx, trapFile, key, symbol.DeclaredAccessibility); + // A file scoped type has declared accessibility `internal` which we shouldn't extract. + // The file modifier is extracted as a source level modifier. + if (symbol.Kind != SymbolKind.NamedType || !((INamedTypeSymbol)symbol).IsFileLocal) + HasAccessibility(cx, trapFile, key, symbol.DeclaredAccessibility); + if (symbol.Kind == SymbolKind.ErrorType) trapFile.has_modifiers(key, Modifier.Create(cx, Accessibility.Public)); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Modifiers.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Modifiers.cs index ef38646bc81..1b07c40fc3a 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Modifiers.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Modifiers.cs @@ -4,6 +4,7 @@ internal static class Modifiers public const string Async = "async"; public const string Const = "const"; public const string Extern = "extern"; + public const string File = "file"; public const string Internal = "internal"; public const string New = "new"; public const string Override = "override"; diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/UserOperator.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/UserOperator.cs index f478d991919..c6e03f86a29 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/UserOperator.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/UserOperator.cs @@ -11,6 +11,8 @@ namespace Semmle.Extraction.CSharp.Entities protected UserOperator(Context cx, IMethodSymbol init) : base(cx, init) { } + protected override MethodKind ExplicitlyImplementsKind => MethodKind.UserDefinedOperator; + public override void Populate(TextWriter trapFile) { PopulateMethod(trapFile); @@ -37,6 +39,7 @@ namespace Semmle.Extraction.CSharp.Entities } ContainingType.PopulateGenerics(); + Overrides(trapFile); } public override bool NeedsPopulation => Context.Defines(Symbol) || IsImplicitOperator(out _); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/SymbolExtensions.cs b/csharp/extractor/Semmle.Extraction.CSharp/SymbolExtensions.cs index debb96ca3b6..cd182fe4640 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/SymbolExtensions.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/SymbolExtensions.cs @@ -282,54 +282,60 @@ namespace Semmle.Extraction.CSharp public static IEnumerable GetTupleElementsMaybeNull(this INamedTypeSymbol type) => type.TupleElements; + private static void BuildQualifierAndName(INamedTypeSymbol named, Context cx, EscapingTextWriter trapFile, ISymbol symbolBeingDefined) + { + if (named.ContainingType is not null) + { + named.ContainingType.BuildOrWriteId(cx, trapFile, symbolBeingDefined, constructUnderlyingTupleType: false); + trapFile.Write('.'); + } + else if (named.ContainingNamespace is not null) + { + if (cx.ShouldAddAssemblyTrapPrefix && named.ContainingAssembly is not null) + BuildAssembly(named.ContainingAssembly, trapFile); + named.ContainingNamespace.BuildNamespace(cx, trapFile); + } + + var name = named.IsFileLocal ? named.MetadataName : named.Name; + trapFile.Write(name); + } + + private static void BuildTupleId(INamedTypeSymbol named, Context cx, EscapingTextWriter trapFile, ISymbol symbolBeingDefined) + { + trapFile.Write('('); + trapFile.BuildList(",", named.GetTupleElementsMaybeNull(), + (i, f) => + { + if (f is null) + { + trapFile.Write($"null({i})"); + } + else + { + trapFile.Write((f.CorrespondingTupleField ?? f).Name); + trapFile.Write(":"); + f.Type.BuildOrWriteId(cx, trapFile, symbolBeingDefined, constructUnderlyingTupleType: false); + } + } + ); + trapFile.Write(")"); + } + private static void BuildNamedTypeId(this INamedTypeSymbol named, Context cx, EscapingTextWriter trapFile, ISymbol symbolBeingDefined, bool constructUnderlyingTupleType) { if (!constructUnderlyingTupleType && named.IsTupleType) { - trapFile.Write('('); - trapFile.BuildList(",", named.GetTupleElementsMaybeNull(), - (i, f) => - { - if (f is null) - { - trapFile.Write($"null({i})"); - } - else - { - trapFile.Write((f.CorrespondingTupleField ?? f).Name); - trapFile.Write(":"); - f.Type.BuildOrWriteId(cx, trapFile, symbolBeingDefined, constructUnderlyingTupleType: false); - } - } - ); - trapFile.Write(")"); + BuildTupleId(named, cx, trapFile, symbolBeingDefined); return; } - void AddContaining() - { - if (named.ContainingType is not null) - { - named.ContainingType.BuildOrWriteId(cx, trapFile, symbolBeingDefined, constructUnderlyingTupleType: false); - trapFile.Write('.'); - } - else if (named.ContainingNamespace is not null) - { - if (cx.ShouldAddAssemblyTrapPrefix && named.ContainingAssembly is not null) - BuildAssembly(named.ContainingAssembly, trapFile); - named.ContainingNamespace.BuildNamespace(cx, trapFile); - } - } - if (named.TypeParameters.IsEmpty) { - AddContaining(); - trapFile.Write(named.Name); + BuildQualifierAndName(named, cx, trapFile, symbolBeingDefined); } else if (named.IsReallyUnbound()) { - AddContaining(); - trapFile.Write(named.Name); + BuildQualifierAndName(named, cx, trapFile, symbolBeingDefined); trapFile.Write("`"); trapFile.Write(named.TypeParameters.Length); } diff --git a/csharp/ql/lib/change-notes/2023-02-17-filescopedtypes.md b/csharp/ql/lib/change-notes/2023-02-17-filescopedtypes.md new file mode 100644 index 00000000000..f3baf13fde4 --- /dev/null +++ b/csharp/ql/lib/change-notes/2023-02-17-filescopedtypes.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* C# 11: Added extractor and library support for `file` scoped types. \ No newline at end of file diff --git a/csharp/ql/lib/change-notes/2023-02-27-explicitinterfacemember.md b/csharp/ql/lib/change-notes/2023-02-27-explicitinterfacemember.md new file mode 100644 index 00000000000..7fd5f2ce8be --- /dev/null +++ b/csharp/ql/lib/change-notes/2023-02-27-explicitinterfacemember.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* C# 11: Support for explicit interface member implementation of operators. \ No newline at end of file diff --git a/csharp/ql/lib/change-notes/2023-02-28-staticfieldwrites.md b/csharp/ql/lib/change-notes/2023-02-28-staticfieldwrites.md new file mode 100644 index 00000000000..8f726f7a6df --- /dev/null +++ b/csharp/ql/lib/change-notes/2023-02-28-staticfieldwrites.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* The query `cs/static-field-written-by-instance` is updated to handle properties. \ No newline at end of file diff --git a/csharp/ql/lib/change-notes/2023-03-02-dataflow-conf-module.md b/csharp/ql/lib/change-notes/2023-03-02-dataflow-conf-module.md new file mode 100644 index 00000000000..89190af399f --- /dev/null +++ b/csharp/ql/lib/change-notes/2023-03-02-dataflow-conf-module.md @@ -0,0 +1,9 @@ +--- +category: majorAnalysis +--- +* The main data flow and taint tracking APIs have been changed. The old APIs + remain in place for now and translate to the new through a + backwards-compatible wrapper. If multiple configurations are in scope + simultaneously, then this may affect results slightly. The new API is quite + similar to the old, but makes use of a configuration module instead of a + configuration class. diff --git a/csharp/ql/lib/semmle/code/csharp/Callable.qll b/csharp/ql/lib/semmle/code/csharp/Callable.qll index 82ffbfa6a06..00091f66ddc 100644 --- a/csharp/ql/lib/semmle/code/csharp/Callable.qll +++ b/csharp/ql/lib/semmle/code/csharp/Callable.qll @@ -434,7 +434,7 @@ class Destructor extends DotNet::Destructor, Callable, Member, Attributable, @de * Either a unary operator (`UnaryOperator`), a binary operator * (`BinaryOperator`), or a conversion operator (`ConversionOperator`). */ -class Operator extends Callable, Member, Attributable, @operator { +class Operator extends Callable, Member, Attributable, Overridable, @operator { /** * DEPRECATED: use `getFunctionName()` instead. * diff --git a/csharp/ql/lib/semmle/code/csharp/Member.qll b/csharp/ql/lib/semmle/code/csharp/Member.qll index 0ce8dc44e3c..2308911e770 100644 --- a/csharp/ql/lib/semmle/code/csharp/Member.qll +++ b/csharp/ql/lib/semmle/code/csharp/Member.qll @@ -93,6 +93,9 @@ class Modifiable extends Declaration, @modifiable { /** Holds if this declaration has the modifier `required`. */ predicate isRequired() { this.hasModifier("required") } + /** Holds if this declaration is `file` local. */ + predicate isFile() { this.hasModifier("file") } + /** Holds if this declaration is `unsafe`. */ predicate isUnsafe() { this.hasModifier("unsafe") or @@ -183,6 +186,8 @@ class Member extends DotNet::Member, Modifiable, @member { override predicate isStatic() { Modifiable.super.isStatic() } override predicate isRequired() { Modifiable.super.isRequired() } + + override predicate isFile() { Modifiable.super.isFile() } } private class TOverridable = @virtualizable or @callable_accessor; diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/DataFlow.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/DataFlow.qll index 1d3d53e2316..847aa850d35 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/DataFlow.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/DataFlow.qll @@ -6,5 +6,6 @@ import csharp module DataFlow { - import semmle.code.csharp.dataflow.internal.DataFlowImpl + import semmle.code.csharp.dataflow.internal.DataFlow + import semmle.code.csharp.dataflow.internal.DataFlowImpl1 } diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/FlowSummary.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/FlowSummary.qll index 8695563f160..65b63958cb9 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/FlowSummary.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/FlowSummary.qll @@ -137,6 +137,8 @@ private class RecordConstructorFlow extends SummarizedCallable { preservesValue = true ) } + + override predicate hasProvenance(string provenance) { provenance = "manual" } } class RequiredSummaryComponentStack = Impl::Public::RequiredSummaryComponentStack; diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/TaintTracking.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/TaintTracking.qll index 97f5073cab0..57f499ffa21 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/TaintTracking.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/TaintTracking.qll @@ -6,5 +6,6 @@ import csharp module TaintTracking { + import semmle.code.csharp.dataflow.internal.tainttracking1.TaintTracking import semmle.code.csharp.dataflow.internal.tainttracking1.TaintTrackingImpl } diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/ContentDataFlow.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/ContentDataFlow.qll index 787f2f614f7..0259c748ec3 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/ContentDataFlow.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/ContentDataFlow.qll @@ -16,8 +16,7 @@ module ContentDataFlow { class ContentSet = DF::ContentSet; - predicate stageStats = DF::stageStats/8; - + // predicate stageStats = DF::stageStats/8; /** * A configuration of interprocedural data flow analysis. This defines * sources, sinks, and any other configurable aspect of the analysis. Each diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlow.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlow.qll new file mode 100644 index 00000000000..fe9b9b18941 --- /dev/null +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlow.qll @@ -0,0 +1,245 @@ +/** + * Provides an implementation of global (interprocedural) data flow. This file + * re-exports the local (intraprocedural) data flow analysis from + * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed + * through the `Make` and `MakeWithState` modules. + */ + +private import DataFlowImplCommon +private import DataFlowImplSpecific::Private +import DataFlowImplSpecific::Public +import DataFlowImplCommonPublic +private import DataFlowImpl + +/** An input configuration for data flow. */ +signature module ConfigSig { + /** + * Holds if `source` is a relevant data flow source. + */ + predicate isSource(Node source); + + /** + * Holds if `sink` is a relevant data flow sink. + */ + predicate isSink(Node sink); + + /** + * Holds if data flow through `node` is prohibited. This completely removes + * `node` from the data flow graph. + */ + default predicate isBarrier(Node node) { none() } + + /** Holds if data flow into `node` is prohibited. */ + default predicate isBarrierIn(Node node) { none() } + + /** Holds if data flow out of `node` is prohibited. */ + default predicate isBarrierOut(Node node) { none() } + + /** + * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. + */ + default predicate isAdditionalFlowStep(Node node1, Node node2) { none() } + + /** + * Holds if an arbitrary number of implicit read steps of content `c` may be + * taken at `node`. + */ + default predicate allowImplicitRead(Node node, ContentSet c) { none() } + + /** + * Gets the virtual dispatch branching limit when calculating field flow. + * This can be overridden to a smaller value to improve performance (a + * value of 0 disables field flow), or a larger value to get more results. + */ + default int fieldFlowBranchLimit() { result = 2 } + + /** + * Gets a data flow configuration feature to add restrictions to the set of + * valid flow paths. + * + * - `FeatureHasSourceCallContext`: + * Assume that sources have some existing call context to disallow + * conflicting return-flow directly following the source. + * - `FeatureHasSinkCallContext`: + * Assume that sinks have some existing call context to disallow + * conflicting argument-to-parameter flow directly preceding the sink. + * - `FeatureEqualSourceSinkCallContext`: + * Implies both of the above and additionally ensures that the entire flow + * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. + */ + default FlowFeature getAFeature() { none() } + + /** Holds if sources should be grouped in the result of `hasFlowPath`. */ + default predicate sourceGrouping(Node source, string sourceGroup) { none() } + + /** Holds if sinks should be grouped in the result of `hasFlowPath`. */ + default predicate sinkGrouping(Node sink, string sinkGroup) { none() } + + /** + * Holds if hidden nodes should be included in the data flow graph. + * + * This feature should only be used for debugging or when the data flow graph + * is not visualized (as it is in a `path-problem` query). + */ + default predicate includeHiddenNodes() { none() } +} + +/** An input configuration for data flow using flow state. */ +signature module StateConfigSig { + bindingset[this] + class FlowState; + + /** + * Holds if `source` is a relevant data flow source with the given initial + * `state`. + */ + predicate isSource(Node source, FlowState state); + + /** + * Holds if `sink` is a relevant data flow sink accepting `state`. + */ + predicate isSink(Node sink, FlowState state); + + /** + * Holds if data flow through `node` is prohibited. This completely removes + * `node` from the data flow graph. + */ + default predicate isBarrier(Node node) { none() } + + /** + * Holds if data flow through `node` is prohibited when the flow state is + * `state`. + */ + predicate isBarrier(Node node, FlowState state); + + /** Holds if data flow into `node` is prohibited. */ + default predicate isBarrierIn(Node node) { none() } + + /** Holds if data flow out of `node` is prohibited. */ + default predicate isBarrierOut(Node node) { none() } + + /** + * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. + */ + default predicate isAdditionalFlowStep(Node node1, Node node2) { none() } + + /** + * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. + * This step is only applicable in `state1` and updates the flow state to `state2`. + */ + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2); + + /** + * Holds if an arbitrary number of implicit read steps of content `c` may be + * taken at `node`. + */ + default predicate allowImplicitRead(Node node, ContentSet c) { none() } + + /** + * Gets the virtual dispatch branching limit when calculating field flow. + * This can be overridden to a smaller value to improve performance (a + * value of 0 disables field flow), or a larger value to get more results. + */ + default int fieldFlowBranchLimit() { result = 2 } + + /** + * Gets a data flow configuration feature to add restrictions to the set of + * valid flow paths. + * + * - `FeatureHasSourceCallContext`: + * Assume that sources have some existing call context to disallow + * conflicting return-flow directly following the source. + * - `FeatureHasSinkCallContext`: + * Assume that sinks have some existing call context to disallow + * conflicting argument-to-parameter flow directly preceding the sink. + * - `FeatureEqualSourceSinkCallContext`: + * Implies both of the above and additionally ensures that the entire flow + * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. + */ + default FlowFeature getAFeature() { none() } + + /** Holds if sources should be grouped in the result of `hasFlowPath`. */ + default predicate sourceGrouping(Node source, string sourceGroup) { none() } + + /** Holds if sinks should be grouped in the result of `hasFlowPath`. */ + default predicate sinkGrouping(Node sink, string sinkGroup) { none() } + + /** + * Holds if hidden nodes should be included in the data flow graph. + * + * This feature should only be used for debugging or when the data flow graph + * is not visualized (as it is in a `path-problem` query). + */ + default predicate includeHiddenNodes() { none() } +} + +/** + * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` + * measured in approximate number of interprocedural steps. + */ +signature int explorationLimitSig(); + +/** + * The output of a data flow computation. + */ +signature module DataFlowSig { + /** + * A `Node` augmented with a call context (except for sinks) and an access path. + * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. + */ + class PathNode; + + /** + * Holds if data can flow from `source` to `sink`. + * + * The corresponding paths are generated from the end-points and the graph + * included in the module `PathGraph`. + */ + predicate hasFlowPath(PathNode source, PathNode sink); + + /** + * Holds if data can flow from `source` to `sink`. + */ + predicate hasFlow(Node source, Node sink); + + /** + * Holds if data can flow from some source to `sink`. + */ + predicate hasFlowTo(Node sink); + + /** + * Holds if data can flow from some source to `sink`. + */ + predicate hasFlowToExpr(DataFlowExpr sink); +} + +/** + * Constructs a standard data flow computation. + */ +module Make implements DataFlowSig { + private module C implements FullStateConfigSig { + import DefaultState + import Config + } + + import Impl +} + +/** + * Constructs a data flow computation using flow state. + */ +module MakeWithState implements DataFlowSig { + private module C implements FullStateConfigSig { + import Config + } + + import Impl +} diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll index 1b969756b09..0afcba55582 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll @@ -1,135 +1,75 @@ /** - * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from - * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed - * through the `Configuration` class. This file exists in several identical - * copies, allowing queries to use multiple `Configuration` classes that depend - * on each other without introducing mutual recursion among those configurations. + * INTERNAL: Do not use. + * + * Provides an implementation of global (interprocedural) data flow. */ private import DataFlowImplCommon private import DataFlowImplSpecific::Private -import DataFlowImplSpecific::Public -import DataFlowImplCommonPublic +private import DataFlowImplSpecific::Public +private import DataFlowImplCommonPublic +import DataFlow /** - * A configuration of interprocedural data flow analysis. This defines - * sources, sinks, and any other configurable aspect of the analysis. Each - * use of the global data flow library must define its own unique extension - * of this abstract class. To create a configuration, extend this class with - * a subclass whose characteristic predicate is a unique singleton string. - * For example, write - * - * ```ql - * class MyAnalysisConfiguration extends DataFlow::Configuration { - * MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" } - * // Override `isSource` and `isSink`. - * // Optionally override `isBarrier`. - * // Optionally override `isAdditionalFlowStep`. - * } - * ``` - * Conceptually, this defines a graph where the nodes are `DataFlow::Node`s and - * the edges are those data-flow steps that preserve the value of the node - * along with any additional edges defined by `isAdditionalFlowStep`. - * Specifying nodes in `isBarrier` will remove those nodes from the graph, and - * specifying nodes in `isBarrierIn` and/or `isBarrierOut` will remove in-going - * and/or out-going edges from those nodes, respectively. - * - * Then, to query whether there is flow between some `source` and `sink`, - * write - * - * ```ql - * exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink)) - * ``` - * - * Multiple configurations can coexist, but two classes extending - * `DataFlow::Configuration` should never depend on each other. One of them - * should instead depend on a `DataFlow2::Configuration`, a - * `DataFlow3::Configuration`, or a `DataFlow4::Configuration`. + * An input configuration for data flow using flow state. This signature equals + * `StateConfigSig`, but requires explicit implementation of all predicates. */ -abstract class Configuration extends string { +signature module FullStateConfigSig { bindingset[this] - Configuration() { any() } - - /** - * Holds if `source` is a relevant data flow source. - */ - predicate isSource(Node source) { none() } + class FlowState; /** * Holds if `source` is a relevant data flow source with the given initial * `state`. */ - predicate isSource(Node source, FlowState state) { none() } - - /** - * Holds if `sink` is a relevant data flow sink. - */ - predicate isSink(Node sink) { none() } + predicate isSource(Node source, FlowState state); /** * Holds if `sink` is a relevant data flow sink accepting `state`. */ - predicate isSink(Node sink, FlowState state) { none() } + predicate isSink(Node sink, FlowState state); /** * Holds if data flow through `node` is prohibited. This completely removes * `node` from the data flow graph. */ - predicate isBarrier(Node node) { none() } + predicate isBarrier(Node node); /** * Holds if data flow through `node` is prohibited when the flow state is * `state`. */ - predicate isBarrier(Node node, FlowState state) { none() } + predicate isBarrier(Node node, FlowState state); /** Holds if data flow into `node` is prohibited. */ - predicate isBarrierIn(Node node) { none() } + predicate isBarrierIn(Node node); /** Holds if data flow out of `node` is prohibited. */ - predicate isBarrierOut(Node node) { none() } - - /** - * DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead. - * - * Holds if data flow through nodes guarded by `guard` is prohibited. - */ - deprecated predicate isBarrierGuard(BarrierGuard guard) { none() } - - /** - * DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead. - * - * Holds if data flow through nodes guarded by `guard` is prohibited when - * the flow state is `state` - */ - deprecated predicate isBarrierGuard(BarrierGuard guard, FlowState state) { none() } + predicate isBarrierOut(Node node); /** * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. */ - predicate isAdditionalFlowStep(Node node1, Node node2) { none() } + predicate isAdditionalFlowStep(Node node1, Node node2); /** * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. * This step is only applicable in `state1` and updates the flow state to `state2`. */ - predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { - none() - } + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2); /** * Holds if an arbitrary number of implicit read steps of content `c` may be * taken at `node`. */ - predicate allowImplicitRead(Node node, ContentSet c) { none() } + predicate allowImplicitRead(Node node, ContentSet c); /** * Gets the virtual dispatch branching limit when calculating field flow. * This can be overridden to a smaller value to improve performance (a * value of 0 disables field flow), or a larger value to get more results. */ - int fieldFlowBranchLimit() { result = 2 } + int fieldFlowBranchLimit(); /** * Gets a data flow configuration feature to add restrictions to the set of @@ -144,1720 +84,680 @@ abstract class Configuration extends string { * - `FeatureEqualSourceSinkCallContext`: * Implies both of the above and additionally ensures that the entire flow * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. */ - FlowFeature getAFeature() { none() } + FlowFeature getAFeature(); /** Holds if sources should be grouped in the result of `hasFlowPath`. */ - predicate sourceGrouping(Node source, string sourceGroup) { none() } + predicate sourceGrouping(Node source, string sourceGroup); /** Holds if sinks should be grouped in the result of `hasFlowPath`. */ - predicate sinkGrouping(Node sink, string sinkGroup) { none() } - - /** - * Holds if data may flow from `source` to `sink` for this configuration. - */ - predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } - - /** - * Holds if data may flow from `source` to `sink` for this configuration. - * - * The corresponding paths are generated from the end-points and the graph - * included in the module `PathGraph`. - */ - predicate hasFlowPath(PathNode source, PathNode sink) { hasFlowPath(source, sink, this) } - - /** - * Holds if data may flow from some source to `sink` for this configuration. - */ - predicate hasFlowTo(Node sink) { - sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode() - } - - /** - * Holds if data may flow from some source to `sink` for this configuration. - */ - predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } - - /** - * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` - * measured in approximate number of interprocedural steps. - */ - int explorationLimit() { none() } + predicate sinkGrouping(Node sink, string sinkGroup); /** * Holds if hidden nodes should be included in the data flow graph. * * This feature should only be used for debugging or when the data flow graph - * is not visualized (for example in a `path-problem` query). + * is not visualized (as it is in a `path-problem` query). */ - predicate includeHiddenNodes() { none() } + predicate includeHiddenNodes(); +} - /** - * Holds if there is a partial data flow path from `source` to `node`. The - * approximate distance between `node` and the closest source is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards sink definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sources is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - */ - final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { - partialFlow(source, node, this) and - dist = node.getSourceDistance() - } +/** + * Provides default `FlowState` implementations given a `StateConfigSig`. + */ +module DefaultState { + class FlowState = Unit; - /** - * Holds if there is a partial data flow path from `node` to `sink`. The - * approximate distance between `node` and the closest sink is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards source definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sinks is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - * - * Note that reverse flow has slightly lower precision than the corresponding - * forward flow, as reverse flow disregards type pruning among other features. - */ - final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { - revPartialFlow(node, sink, this) and - dist = node.getSinkDistance() + predicate isSource(Node source, FlowState state) { Config::isSource(source) and exists(state) } + + predicate isSink(Node sink, FlowState state) { Config::isSink(sink) and exists(state) } + + predicate isBarrier(Node node, FlowState state) { none() } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + none() } } /** - * This class exists to prevent mutual recursion between the user-overridden - * member predicates of `Configuration` and the rest of the data-flow library. - * Good performance cannot be guaranteed in the presence of such recursion, so - * it should be replaced by using more than one copy of the data flow library. + * Constructs a data flow computation given a full input configuration. */ -abstract private class ConfigurationRecursionPrevention extends Configuration { - bindingset[this] - ConfigurationRecursionPrevention() { any() } +module Impl { + private class FlowState = Config::FlowState; - override predicate hasFlow(Node source, Node sink) { - strictcount(Node n | this.isSource(n)) < 0 - or - strictcount(Node n | this.isSource(n, _)) < 0 - or - strictcount(Node n | this.isSink(n)) < 0 - or - strictcount(Node n | this.isSink(n, _)) < 0 - or - strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0 - or - strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, _, n2, _)) < 0 - or - super.hasFlow(source, sink) - } -} + private newtype TNodeEx = + TNodeNormal(Node n) or + TNodeImplicitRead(Node n, boolean hasRead) { + Config::allowImplicitRead(n, _) and hasRead = [false, true] + } -private newtype TNodeEx = - TNodeNormal(Node n) or - TNodeImplicitRead(Node n, boolean hasRead) { - any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true] - } - -private class NodeEx extends TNodeEx { - string toString() { - result = this.asNode().toString() - or - exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") - } - - Node asNode() { this = TNodeNormal(result) } - - predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } - - Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } - - pragma[nomagic] - private DataFlowCallable getEnclosingCallable0() { - nodeEnclosingCallable(this.projectToNode(), result) - } - - pragma[inline] - DataFlowCallable getEnclosingCallable() { - pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) - } - - pragma[nomagic] - private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } - - pragma[inline] - DataFlowType getDataFlowType() { - pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) - } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -private class ArgNodeEx extends NodeEx { - ArgNodeEx() { this.asNode() instanceof ArgNode } -} - -private class ParamNodeEx extends NodeEx { - ParamNodeEx() { this.asNode() instanceof ParamNode } - - predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { - this.asNode().(ParamNode).isParameterOf(c, pos) - } - - ParameterPosition getPosition() { this.isParameterOf(_, result) } -} - -private class RetNodeEx extends NodeEx { - RetNodeEx() { this.asNode() instanceof ReturnNodeExt } - - ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } - - ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } -} - -private predicate inBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierIn(n) - | - config.isSource(n) or config.isSource(n, _) - ) -} - -private predicate outBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierOut(n) - | - config.isSink(n) or config.isSink(n, _) - ) -} - -/** A bridge class to access the deprecated `isBarrierGuard`. */ -private class BarrierGuardGuardedNodeBridge extends Unit { - abstract predicate guardedNode(Node n, Configuration config); - - abstract predicate guardedNode(Node n, FlowState state, Configuration config); -} - -private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { - deprecated override predicate guardedNode(Node n, Configuration config) { - exists(BarrierGuard g | - config.isBarrierGuard(g) and - n = g.getAGuardedNode() - ) - } - - deprecated override predicate guardedNode(Node n, FlowState state, Configuration config) { - exists(BarrierGuard g | - config.isBarrierGuard(g, state) and - n = g.getAGuardedNode() - ) - } -} - -pragma[nomagic] -private predicate fullBarrier(NodeEx node, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n) - or - config.isBarrierIn(n) and - not config.isSource(n) and - not config.isSource(n, _) - or - config.isBarrierOut(n) and - not config.isSink(n) and - not config.isSink(n, _) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config) - ) -} - -pragma[nomagic] -private predicate stateBarrier(NodeEx node, FlowState state, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n, state) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config) - ) -} - -pragma[nomagic] -private predicate sourceNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSource(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSource(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -pragma[nomagic] -private predicate sinkNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSink(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSink(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -/** Provides the relevant barriers for a step from `node1` to `node2`. */ -pragma[inline] -private predicate stepFilter(NodeEx node1, NodeEx node2, Configuration config) { - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) -} - -/** - * Holds if data can flow in one local step from `node1` to `node2`. - */ -private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.asNode() = n and - node2.isImplicitReadNode(n, false) and - not fullBarrier(node1, config) - ) -} - -/** - * Holds if the additional step from `node1` to `node2` does not jump between callables. - */ -private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.isImplicitReadNode(n, true) and - node2.asNode() = n and - not fullBarrier(node2, config) - ) -} - -private predicate additionalLocalStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) - ) -} - -/** - * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. - */ -private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if the additional step from `node1` to `node2` jumps between callables. - */ -private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate additionalJumpStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -pragma[nomagic] -private predicate readSet(NodeEx node1, ContentSet c, NodeEx node2, Configuration config) { - readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and - stepFilter(node1, node2, config) - or - exists(Node n | - node2.isImplicitReadNode(n, true) and - node1.isImplicitReadNode(n, _) and - config.allowImplicitRead(n, c) - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(ContentSet cs | - readSet(node1, cs, node2, config) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate clearsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - clearsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate expectsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - expectsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -pragma[nomagic] -private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } - -pragma[nomagic] -private predicate hasReadStep(Content c, Configuration config) { read(_, c, _, config) } - -pragma[nomagic] -private predicate store( - NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config -) { - store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), - contentType) and - hasReadStep(tc.getContent(), config) and - stepFilter(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { - viableReturnPosOut(call, pos, out.asNode()) -} - -pragma[nomagic] -private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { - viableParamArg(call, p.asNode(), arg.asNode()) -} - -/** - * Holds if field flow should be used for the given configuration. - */ -private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } - -private predicate hasSourceCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSourceCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate hasSinkCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSinkCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if flow from `p` to a return node of kind `kind` is allowed. - * - * We don't expect a parameter to return stored in itself, unless - * explicitly allowed - */ -bindingset[p, kind] -private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { - exists(ParameterPosition pos | p.isParameterOf(_, pos) | - not kind.(ParamUpdateReturnKind).getPosition() = pos - or - allowParameterReturnInSelfCached(p.asNode()) - ) -} - -private module Stage1 implements StageSig { - class Ap extends int { - // workaround for bad functionality-induced joins (happens when using `Unit`) - pragma[nomagic] - Ap() { this in [0 .. 1] and this < 1 } - } - - private class Cc = boolean; - - /* Begin: Stage 1 logic. */ - /** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `cc` records whether the node is reached through an - * argument in a call. - */ - private predicate fwdFlow(NodeEx node, Cc cc, Configuration config) { - sourceNode(node, _, config) and - if hasSourceCallCtx(config) then cc = true else cc = false - or - exists(NodeEx mid | fwdFlow(mid, cc, config) | - localFlowStep(mid, node, config) or - additionalLocalFlowStep(mid, node, config) or - additionalLocalStateStep(mid, _, node, _, config) - ) - or - exists(NodeEx mid | fwdFlow(mid, _, config) and cc = false | - jumpStep(mid, node, config) or - additionalJumpStep(mid, node, config) or - additionalJumpStateStep(mid, _, node, _, config) - ) - or - // store - exists(NodeEx mid | - useFieldFlow(config) and - fwdFlow(mid, cc, config) and - store(mid, _, node, _, config) - ) - or - // read - exists(ContentSet c | - fwdFlowReadSet(c, node, cc, config) and - fwdFlowConsCandSet(c, _, config) - ) - or - // flow into a callable - fwdFlowIn(_, _, _, node, config) and - cc = true - or - // flow out of a callable - fwdFlowOut(_, node, false, config) and - cc = false - or - // flow through a callable - exists(DataFlowCall call | - fwdFlowOutFromArg(call, node, config) and - fwdFlowIsEntered(call, cc, config) - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowIn( - DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p, Configuration config - ) { - // call context cannot help reduce virtual dispatch - fwdFlow(arg, cc, config) and - viableParamArgEx(call, p, arg) and - not fullBarrier(p, config) and - ( - cc = false + private class NodeEx extends TNodeEx { + string toString() { + result = this.asNode().toString() or - cc = true and - not reducedViableImplInCallContext(call, _, _) - ) - or - // call context may help reduce virtual dispatch - exists(DataFlowCallable target | - fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target, config) and - target = viableImplInSomeFwdFlowCallContextExt(call, config) and - cc = true - ) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - fwdFlowIn(call, _, cc, _, config) - } - - pragma[nomagic] - private predicate fwdFlowInReducedViableImplInSomeCallContext( - DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target, Configuration config - ) { - fwdFlow(arg, true, config) and - viableParamArgEx(call, p, arg) and - reducedViableImplInCallContext(call, _, _) and - target = p.getEnclosingCallable() and - not fullBarrier(p, config) - } - - /** - * Gets a viable dispatch target of `call` in the context `ctx`. This is - * restricted to those `call`s for which a context might make a difference, - * and to `ctx`s that are reachable in `fwdFlow`. - */ - pragma[nomagic] - private DataFlowCallable viableImplInSomeFwdFlowCallContextExt( - DataFlowCall call, Configuration config - ) { - exists(DataFlowCall ctx | - fwdFlowIsEntered(ctx, _, config) and - result = viableImplInCallContextExt(call, ctx) - ) - } - - private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) } - - pragma[nomagic] - private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc, Configuration config) { - exists(NodeEx mid | - fwdFlow(mid, cc, config) and - readSet(mid, c, node, config) - ) - } - - /** - * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - fwdFlow(mid, _, config) and - store(mid, tc, node, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `cs` may be interpreted in a read as the target of some store - * into `c`, in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCandSet(ContentSet cs, Content c, Configuration config) { - fwdFlowConsCand(c, config) and - c = cs.getAReadContent() - } - - pragma[nomagic] - private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { - exists(RetNodeEx ret | - fwdFlow(ret, cc, config) and - ret.getReturnPosition() = pos - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) { - exists(ReturnPosition pos | - fwdFlowReturnPosition(pos, cc, config) and - viableReturnPosOutEx(call, pos, out) and - not fullBarrier(out, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) { - fwdFlowOut(call, out, true, config) - } - - private predicate stateStepFwd(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1 | - additionalLocalStateStep(node1, state1, _, state2, config) or - additionalJumpStateStep(node1, state1, _, state2, config) - | - fwdFlow(node1, config) - ) - } - - private predicate fwdFlowState(FlowState state, Configuration config) { - sourceNode(_, state, config) - or - exists(FlowState state0 | - fwdFlowState(state0, config) and - stateStepFwd(state0, state, config) - ) - } - - /** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink. - */ - pragma[nomagic] - private predicate revFlow(NodeEx node, boolean toReturn, Configuration config) { - revFlow0(node, toReturn, config) and - fwdFlow(node, config) - } - - pragma[nomagic] - private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) { - exists(FlowState state | - fwdFlow(node, pragma[only_bind_into](config)) and - sinkNode(node, state, config) and - fwdFlowState(state, pragma[only_bind_into](config)) and - if hasSinkCallCtx(config) then toReturn = true else toReturn = false - ) - or - exists(NodeEx mid | revFlow(mid, toReturn, config) | - localFlowStep(node, mid, config) or - additionalLocalFlowStep(node, mid, config) or - additionalLocalStateStep(node, _, mid, _, config) - ) - or - exists(NodeEx mid | revFlow(mid, _, config) and toReturn = false | - jumpStep(node, mid, config) or - additionalJumpStep(node, mid, config) or - additionalJumpStateStep(node, _, mid, _, config) - ) - or - // store - exists(Content c | - revFlowStore(c, node, toReturn, config) and - revFlowConsCand(c, config) - ) - or - // read - exists(NodeEx mid, ContentSet c | - readSet(node, c, mid, config) and - fwdFlowConsCandSet(c, _, pragma[only_bind_into](config)) and - revFlow(mid, toReturn, pragma[only_bind_into](config)) - ) - or - // flow into a callable - revFlowIn(_, node, false, config) and - toReturn = false - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(pos, config) and - node.(RetNodeEx).getReturnPosition() = pos and - toReturn = true - ) - or - // flow through a callable - exists(DataFlowCall call | - revFlowInToReturn(call, node, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - /** - * Holds if `c` is the target of a read in the flow covered by `revFlow`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, ContentSet cs | - fwdFlow(node, pragma[only_bind_into](config)) and - readSet(node, cs, mid, config) and - fwdFlowConsCandSet(cs, c, pragma[only_bind_into](config)) and - revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) { - exists(NodeEx mid, TypedContent tc | - revFlow(mid, toReturn, pragma[only_bind_into](config)) and - fwdFlowConsCand(c, pragma[only_bind_into](config)) and - store(node, tc, mid, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `c` is the target of both a read and a store in the flow covered - * by `revFlow`. - */ - pragma[nomagic] - additional predicate revFlowIsReadAndStored(Content c, Configuration conf) { - revFlowConsCand(c, conf) and - revFlowStore(c, _, _, conf) - } - - pragma[nomagic] - additional predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config - ) { - fwdFlowReturnPosition(pos, _, config) and - viableReturnPosOutEx(call, pos, out) - } - - pragma[nomagic] - private predicate revFlowOut(ReturnPosition pos, Configuration config) { - exists(NodeEx out | - revFlow(out, _, config) and - viableReturnPosOutNodeCandFwd1(_, pos, out, config) - ) - } - - pragma[nomagic] - additional predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config - ) { - fwdFlowIn(call, arg, _, p, config) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate revFlowIn( - DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config - ) { - exists(ParamNodeEx p | - revFlow(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) { - revFlowIn(call, arg, true, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(NodeEx out | - revFlow(out, toReturn, config) and - fwdFlowOutFromArg(call, out, config) - ) - } - - private predicate stateStepRev(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - additionalLocalStateStep(node1, state1, node2, state2, config) or - additionalJumpStateStep(node1, state1, node2, state2, config) - | - revFlow(node1, _, pragma[only_bind_into](config)) and - revFlow(node2, _, pragma[only_bind_into](config)) and - fwdFlowState(state1, pragma[only_bind_into](config)) and - fwdFlowState(state2, pragma[only_bind_into](config)) - ) - } - - additional predicate revFlowState(FlowState state, Configuration config) { - exists(NodeEx node | - sinkNode(node, state, config) and - revFlow(node, _, pragma[only_bind_into](config)) and - fwdFlowState(state, pragma[only_bind_into](config)) - ) - or - exists(FlowState state0 | - revFlowState(state0, config) and - stateStepRev(state, state0, config) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Content c | - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - revFlow(node2, pragma[only_bind_into](config)) and - store(node1, tc, node2, contentType, config) and - c = tc.getContent() and - exists(ap1) - ) - } - - pragma[nomagic] - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) { - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - read(n1, c, n2, pragma[only_bind_into](config)) and - revFlow(n2, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, config) and - exists(ap) - } - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, _, pragma[only_bind_into](config)) and - exists(state) and - exists(ap) - } - - private predicate throughFlowNodeCand(NodeEx node, Configuration config) { - revFlow(node, true, config) and - fwdFlow(node, true, config) and - not inBarrier(node, config) and - not outBarrier(node, config) - } - - /** Holds if flow may return from `callable`. */ - pragma[nomagic] - private predicate returnFlowCallableNodeCand( - DataFlowCallable callable, ReturnKindExt kind, Configuration config - ) { - exists(RetNodeEx ret | - throughFlowNodeCand(ret, config) and - callable = ret.getEnclosingCallable() and - kind = ret.getKind() - ) - } - - /** - * Holds if flow may enter through `p` and reach a return node making `p` a - * candidate for the origin of a summary. - */ - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(DataFlowCallable c, ReturnKindExt kind | - throughFlowNodeCand(p, config) and - returnFlowCallableNodeCand(c, kind, config) and - p.getEnclosingCallable() = c and - exists(ap) and - parameterFlowThroughAllowed(p, kind) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - throughFlowNodeCand(ret, config) and - kind = ret.getKind() and - exists(argAp) and - exists(ap) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, boolean toReturn | - revFlow(arg, toReturn, config) and - revFlowInToReturn(call, arg, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, config)) and - fields = count(Content f0 | fwdFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | fwdFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, config)) and - fields = count(Content f0 | revFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | revFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | revFlow(n, b, config)) - } - /* End: Stage 1 logic. */ -} - -pragma[noinline] -private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - localFlowStep(node1, node2, config) -} - -pragma[noinline] -private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - additionalLocalFlowStep(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCand1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config -) { - Stage1::revFlow(out, config) and - Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, Configuration config -) { - exists(ReturnPosition pos | - viableReturnPosOutNodeCand1(call, pos, out, config) and - pos = ret.getReturnPosition() and - kind = pos.getKind() and - Stage1::revFlow(ret, config) and - not outBarrier(ret, config) and - not inBarrier(out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCand1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config -) { - Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and - Stage1::revFlow(arg, config) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config -) { - viableParamArgNodeCand1(call, p, arg, config) and - Stage1::revFlow(p, config) and - not outBarrier(arg, config) and - not inBarrier(p, config) -} - -/** - * Gets the amount of forward branching on the origin of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int branch(NodeEx n1, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n1, _, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) - ) -} - -/** - * Gets the amount of backward branching on the target of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int join(NodeEx n2, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n, _, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) - ) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. The - * `allowsFieldFlow` flag indicates whether the branching is within the limit - * specified by the configuration. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, ret, kind, out, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(ret, pragma[only_bind_into](config)) and - j = join(out, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. The `allowsFieldFlow` flag indicates whether - * the branching is within the limit specified by the configuration. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config -) { - flowIntoCallNodeCand1(call, arg, p, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(arg, pragma[only_bind_into](config)) and - j = join(p, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -private signature module StageSig { - class Ap; - - predicate revFlow(NodeEx node, Configuration config); - - predicate revFlowAp(NodeEx node, Ap ap, Configuration config); - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config); - - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config); - - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config); - - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ); - - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ); - - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config); -} - -private module MkStage { - class ApApprox = PrevStage::Ap; - - signature module StageParam { - class Ap; - - class ApNil extends Ap; - - bindingset[result, ap] - ApApprox getApprox(Ap ap); - - ApNil getApNil(NodeEx node); - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail); - - /** - * An approximation of `Content` that corresponds to the precision level of - * `Ap`, such that the mappings from both `Ap` and `Content` to this type - * are functional. - */ - class ApHeadContent; - - ApHeadContent getHeadContent(Ap ap); - - ApHeadContent projectToHeadContent(Content c); - - class ApOption; - - ApOption apNone(); - - ApOption apSome(Ap ap); - - class Cc; - - class CcCall extends Cc; - - // TODO: member predicate on CcCall - predicate matchesCall(CcCall cc, DataFlowCall call); - - class CcNoCall extends Cc; - - Cc ccNone(); - - CcCall ccSomeCall(); - - class LocalCc; - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc); - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ); - - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config - ); - - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config - ); - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config); - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType); - } - - module Stage implements StageSig { - import Param - - /* Begin: Stage logic. */ - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - private predicate revFlowApAlias(NodeEx node, ApApprox apa, Configuration config) { - PrevStage::revFlowAp(node, apa, config) + exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") } - pragma[nomagic] - private predicate flowIntoCallApa( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa, - Configuration config - ) { - flowIntoCall(call, arg, p, allowsFieldFlow, config) and - PrevStage::revFlowAp(p, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(arg, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } + Node asNode() { this = TNodeNormal(result) } + + predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } + + Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } pragma[nomagic] - private predicate flowOutOfCallApa( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - ApApprox apa, Configuration config - ) { - flowOutOfCall(call, ret, kind, out, allowsFieldFlow, config) and - PrevStage::revFlowAp(out, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(ret, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowThroughOutOfCall( - DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, - ApApprox argApa, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa, pragma[only_bind_into](config)) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) and - matchesCall(ccc, call) - ) - } - - /** - * Holds if `node` is reachable with access path `ap` from a source in the - * configuration `config`. - * - * The call context `cc` records whether the node is reached through an - * argument in a call, and if so, `summaryCtx` and `argAp` record the - * corresponding parameter position and access path of that argument, respectively. - */ - pragma[nomagic] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa, config) and - PrevStage::revFlow(node, state, apa, config) and - filter(node, state, ap, config) + private DataFlowCallable getEnclosingCallable0() { + nodeEnclosingCallable(this.projectToNode(), result) } pragma[inline] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, cc, summaryCtx, argAp, ap, _, config) + DataFlowCallable getEnclosingCallable() { + pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) } pragma[nomagic] - private predicate fwdFlow0( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config + private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } + + pragma[inline] + DataFlowType getDataFlowType() { + pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) + } + + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn ) { - sourceNode(node, state, config) and - (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and - argAp = apNone() and - summaryCtx = TParamNodeNone() and - ap = getApNil(node) and - apa = getApprox(ap) + this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + private class ArgNodeEx extends NodeEx { + ArgNodeEx() { this.asNode() instanceof ArgNode } + } + + private class ParamNodeEx extends NodeEx { + ParamNodeEx() { this.asNode() instanceof ParamNode } + + predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { + this.asNode().(ParamNode).isParameterOf(c, pos) + } + + ParameterPosition getPosition() { this.isParameterOf(_, result) } + } + + private class RetNodeEx extends NodeEx { + RetNodeEx() { this.asNode() instanceof ReturnNodeExt } + + ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } + + ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } + } + + private predicate inBarrier(NodeEx node) { + exists(Node n | + node.asNode() = n and + Config::isBarrierIn(n) and + Config::isSource(n, _) + ) + } + + private predicate outBarrier(NodeEx node) { + exists(Node n | + node.asNode() = n and + Config::isBarrierOut(n) and + Config::isSink(n, _) + ) + } + + pragma[nomagic] + private predicate fullBarrier(NodeEx node) { + exists(Node n | node.asNode() = n | + Config::isBarrier(n) or - exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | - fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0, config) and - localCc = getLocalCc(mid, cc) - | - localStep(mid, state0, node, state, true, _, config, localCc) and - ap = ap0 and - apa = apa0 - or - localStep(mid, state0, node, state, false, ap, config, localCc) and - ap0 instanceof ApNil and - apa = getApprox(ap) + Config::isBarrierIn(n) and + not Config::isSource(n, _) + or + Config::isBarrierOut(n) and + not Config::isSink(n, _) + ) + } + + pragma[nomagic] + private predicate stateBarrier(NodeEx node, FlowState state) { + exists(Node n | node.asNode() = n | Config::isBarrier(n, state)) + } + + pragma[nomagic] + private predicate sourceNode(NodeEx node, FlowState state) { + Config::isSource(node.asNode(), state) and + not fullBarrier(node) and + not stateBarrier(node, state) + } + + pragma[nomagic] + private predicate sinkNode(NodeEx node, FlowState state) { + Config::isSink(node.asNode(), state) and + not fullBarrier(node) and + not stateBarrier(node, state) + } + + /** Provides the relevant barriers for a step from `node1` to `node2`. */ + pragma[inline] + private predicate stepFilter(NodeEx node1, NodeEx node2) { + not outBarrier(node1) and + not inBarrier(node2) and + not fullBarrier(node1) and + not fullBarrier(node2) + } + + /** + * Holds if data can flow in one local step from `node1` to `node2`. + */ + private predicate localFlowStepEx(NodeEx node1, NodeEx node2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and + stepFilter(node1, node2) + ) + or + exists(Node n | + Config::allowImplicitRead(n, _) and + node1.asNode() = n and + node2.isImplicitReadNode(n, false) and + not fullBarrier(node1) + ) + } + + /** + * Holds if the additional step from `node1` to `node2` does not jump between callables. + */ + private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + Config::isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and + getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and + stepFilter(node1, node2) + ) + or + exists(Node n | + Config::allowImplicitRead(n, _) and + node1.isImplicitReadNode(n, true) and + node2.asNode() = n and + not fullBarrier(node2) + ) + } + + private predicate additionalLocalStateStep(NodeEx node1, FlowState s1, NodeEx node2, FlowState s2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + Config::isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and + getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and + stepFilter(node1, node2) and + not stateBarrier(node1, s1) and + not stateBarrier(node2, s2) + ) + } + + /** + * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. + */ + private predicate jumpStepEx(NodeEx node1, NodeEx node2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and + stepFilter(node1, node2) and + not Config::getAFeature() instanceof FeatureEqualSourceSinkCallContext + ) + } + + /** + * Holds if the additional step from `node1` to `node2` jumps between callables. + */ + private predicate additionalJumpStep(NodeEx node1, NodeEx node2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + Config::isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and + getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and + stepFilter(node1, node2) and + not Config::getAFeature() instanceof FeatureEqualSourceSinkCallContext + ) + } + + private predicate additionalJumpStateStep(NodeEx node1, FlowState s1, NodeEx node2, FlowState s2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + Config::isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and + getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and + stepFilter(node1, node2) and + not stateBarrier(node1, s1) and + not stateBarrier(node2, s2) and + not Config::getAFeature() instanceof FeatureEqualSourceSinkCallContext + ) + } + + pragma[nomagic] + private predicate readSetEx(NodeEx node1, ContentSet c, NodeEx node2) { + readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and + stepFilter(node1, node2) + or + exists(Node n | + node2.isImplicitReadNode(n, true) and + node1.isImplicitReadNode(n, _) and + Config::allowImplicitRead(n, c) + ) + } + + // inline to reduce fan-out via `getAReadContent` + bindingset[c] + private predicate read(NodeEx node1, Content c, NodeEx node2) { + exists(ContentSet cs | + readSetEx(node1, cs, node2) and + pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() + ) + } + + // inline to reduce fan-out via `getAReadContent` + bindingset[c] + private predicate clearsContentEx(NodeEx n, Content c) { + exists(ContentSet cs | + clearsContentCached(n.asNode(), cs) and + pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() + ) + } + + // inline to reduce fan-out via `getAReadContent` + bindingset[c] + private predicate expectsContentEx(NodeEx n, Content c) { + exists(ContentSet cs | + expectsContentCached(n.asNode(), cs) and + pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() + ) + } + + pragma[nomagic] + private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } + + pragma[nomagic] + private predicate hasReadStep(Content c) { read(_, c, _) } + + pragma[nomagic] + private predicate storeEx(NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType) { + store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), + contentType) and + hasReadStep(tc.getContent()) and + stepFilter(node1, node2) + } + + pragma[nomagic] + private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { + viableReturnPosOut(call, pos, out.asNode()) + } + + pragma[nomagic] + private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { + viableParamArg(call, p.asNode(), arg.asNode()) + } + + /** + * Holds if field flow should be used for the given configuration. + */ + private predicate useFieldFlow() { Config::fieldFlowBranchLimit() >= 1 } + + private predicate hasSourceCallCtx() { + exists(FlowFeature feature | feature = Config::getAFeature() | + feature instanceof FeatureHasSourceCallContext or + feature instanceof FeatureEqualSourceSinkCallContext + ) + } + + private predicate hasSinkCallCtx() { + exists(FlowFeature feature | feature = Config::getAFeature() | + feature instanceof FeatureHasSinkCallContext or + feature instanceof FeatureEqualSourceSinkCallContext + ) + } + + /** + * Holds if flow from `p` to a return node of kind `kind` is allowed. + * + * We don't expect a parameter to return stored in itself, unless + * explicitly allowed + */ + bindingset[p, kind] + private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { + exists(ParameterPosition pos | p.isParameterOf(_, pos) | + not kind.(ParamUpdateReturnKind).getPosition() = pos + or + allowParameterReturnInSelfCached(p.asNode()) + ) + } + + private module Stage1 implements StageSig { + class Ap extends int { + // workaround for bad functionality-induced joins (happens when using `Unit`) + pragma[nomagic] + Ap() { this in [0 .. 1] and this < 1 } + } + + private class Cc = boolean; + + /* Begin: Stage 1 logic. */ + /** + * Holds if `node` is reachable from a source. + * + * The Boolean `cc` records whether the node is reached through an + * argument in a call. + */ + private predicate fwdFlow(NodeEx node, Cc cc) { + sourceNode(node, _) and + if hasSourceCallCtx() then cc = true else cc = false + or + exists(NodeEx mid | fwdFlow(mid, cc) | + localFlowStepEx(mid, node) or + additionalLocalFlowStep(mid, node) or + additionalLocalStateStep(mid, _, node, _) ) or - exists(NodeEx mid | - fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa, pragma[only_bind_into](config)) and - jumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(mid, state, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(mid, state0, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStateStep(mid, state0, node, state, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) + exists(NodeEx mid | fwdFlow(mid, _) and cc = false | + jumpStepEx(mid, node) or + additionalJumpStep(mid, node) or + additionalJumpStateStep(mid, _, node, _) ) or // store - exists(TypedContent tc, Ap ap0 | - fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp, config) and - ap = apCons(tc, ap0) and - apa = getApprox(ap) + exists(NodeEx mid | + useFieldFlow() and + fwdFlow(mid, cc) and + storeEx(mid, _, node, _) ) or // read - exists(Ap ap0, Content c | - fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp, config) and - fwdFlowConsCand(ap0, c, ap, config) and - apa = getApprox(ap) + exists(ContentSet c | + fwdFlowReadSet(c, node, cc) and + fwdFlowConsCandSet(c, _) ) or // flow into a callable - fwdFlowIn(_, node, state, _, cc, _, _, ap, apa, config) and - if PrevStage::parameterMayFlowThrough(node, apa, config) - then ( - summaryCtx = TParamNodeSome(node.asNode()) and - argAp = apSome(ap) - ) else ( - summaryCtx = TParamNodeNone() and argAp = apNone() - ) + fwdFlowIn(_, _, _, node) and + cc = true or // flow out of a callable - exists( - DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, - DataFlowCallable inner - | - fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa, config) and - flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa, config) and - inner = ret.getEnclosingCallable() and - cc = getCallContextReturn(inner, call, innercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) + fwdFlowOut(_, node, false) and + cc = false or // flow through a callable - exists( - DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, ApApprox innerArgApa - | - fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowStore( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(DataFlowType contentType, ApApprox apa1 | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1, config) and - PrevStage::storeStepCand(node1, apa1, tc, node2, contentType, config) and - typecheckStore(ap1, contentType) - ) - } - - /** - * Holds if forward flow with access path `tail` reaches a store of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(TypedContent tc | - fwdFlowStore(_, tail, tc, _, _, _, _, _, config) and - tc.getContent() = c and - cons = apCons(tc, tail) - ) - } - - pragma[nomagic] - private predicate readStepCand( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - PrevStage::readStepCand(node1, c, node2, config) and - apc = projectToHeadContent(c) - } - - bindingset[node1, apc] - pragma[inline_late] - private predicate readStepCand0( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - readStepCand(node1, apc, c, node2, config) - } - - pragma[nomagic] - private predicate fwdFlowRead( - Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(ApHeadContent apc | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap, config) and - apc = getHeadContent(ap) and - readStepCand0(node1, apc, c, node2, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowIn( - DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, - ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa, Configuration config - ) { - exists(ArgNodeEx arg, boolean allowsFieldFlow | - fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa, config) and - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowRetFromArg( - RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, ApApprox argApa, - Ap ap, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - fwdFlow(pragma[only_bind_into](ret), state, ccc, - TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), - pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa), - pragma[only_bind_into](config)) and - kind = ret.getKind() and - parameterFlowThroughAllowed(summaryCtx, kind) and - argApa = getApprox(argAp) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) + exists(DataFlowCall call | + fwdFlowOutFromArg(call, node) and + fwdFlowIsEntered(call, cc) ) } + // inline to reduce the number of iterations pragma[inline] - private predicate fwdFlowThrough0( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, - Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa, config) and - fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp, config) - } - - pragma[nomagic] - private predicate fwdFlowThrough( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa, - config) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` - * and data might flow through the target callable and back out at `call`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered( - DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, - ParamNodeEx p, Ap ap, Configuration config - ) { - exists(ApApprox apa | - fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(p, apa, config) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate storeStepFwd( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config - ) { - fwdFlowStore(node1, ap1, tc, node2, _, _, _, _, config) and - ap2 = apCons(tc, ap1) and - fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _, config) - } - - private predicate readStepFwd( - NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config - ) { - fwdFlowRead(ap1, c, n1, n2, _, _, _, _, config) and - fwdFlowConsCand(ap1, c, ap2, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough0( - DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, - ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, - innerArgApa, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough( - RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, - Ap ap, Configuration config - ) { - exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | - returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa, config) and - pos = ret.getReturnPosition() and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowThroughIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap, - Configuration config - ) { - exists(ApApprox argApa | - flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), - allowsFieldFlow, argApa, pragma[only_bind_into](config)) and - fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa, - pragma[only_bind_into](config)) and - returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap, - pragma[only_bind_into](config)) and - if allowsFieldFlow = false then argAp instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowIntoCallAp( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap, - Configuration config - ) { - exists(ApApprox apa | - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - fwdFlow(arg, _, _, _, _, ap, apa, config) - ) - } - - pragma[nomagic] - private predicate flowOutOfCallAp( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, - Ap ap, Configuration config - ) { - exists(ApApprox apa | - flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa, config) and - fwdFlow(ret, _, _, _, _, ap, apa, config) and - pos = ret.getReturnPosition() - ) - } - - /** - * Holds if `node` with access path `ap` is part of a path from a source to a - * sink in the configuration `config`. - * - * The parameter `returnCtx` records whether (and how) the node must be returned - * from the enclosing callable in order to reach a sink, and if so, `returnAp` - * records the access path of the returned value. - */ - pragma[nomagic] - additional predicate revFlow( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - revFlow0(node, state, returnCtx, returnAp, ap, config) and - fwdFlow(node, state, _, _, _, ap, config) - } - - pragma[nomagic] - private predicate revFlow0( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, _, _, _, ap, config) and - sinkNode(node, state, config) and + private predicate fwdFlowIn(DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p) { + // call context cannot help reduce virtual dispatch + fwdFlow(arg, cc) and + viableParamArgEx(call, p, arg) and + not fullBarrier(p) and ( - if hasSinkCallCtx(config) - then returnCtx = TReturnCtxNoFlowThrough() - else returnCtx = TReturnCtxNone() - ) and - returnAp = apNone() and - ap instanceof ApNil - or - exists(NodeEx mid, FlowState state0 | - localStep(node, state, mid, state0, true, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, ap, config) + cc = false + or + cc = true and + not reducedViableImplInCallContext(call, _, _) ) or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap, pragma[only_bind_into](config)) and - localStep(node, pragma[only_bind_into](state), mid, state0, false, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, nil, pragma[only_bind_into](config)) and - ap instanceof ApNil + // call context may help reduce virtual dispatch + exists(DataFlowCallable target | + fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target) and + target = viableImplInSomeFwdFlowCallContextExt(call) and + cc = true ) - or + } + + /** + * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. + */ + pragma[nomagic] + private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc) { fwdFlowIn(call, _, cc, _) } + + pragma[nomagic] + private predicate fwdFlowInReducedViableImplInSomeCallContext( + DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target + ) { + fwdFlow(arg, true) and + viableParamArgEx(call, p, arg) and + reducedViableImplInCallContext(call, _, _) and + target = p.getEnclosingCallable() and + not fullBarrier(p) + } + + /** + * Gets a viable dispatch target of `call` in the context `ctx`. This is + * restricted to those `call`s for which a context might make a difference, + * and to `ctx`s that are reachable in `fwdFlow`. + */ + pragma[nomagic] + private DataFlowCallable viableImplInSomeFwdFlowCallContextExt(DataFlowCall call) { + exists(DataFlowCall ctx | + fwdFlowIsEntered(ctx, _) and + result = viableImplInCallContextExt(call, ctx) + ) + } + + private predicate fwdFlow(NodeEx node) { fwdFlow(node, _) } + + pragma[nomagic] + private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc) { exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, state, _, _, ap, config) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() + fwdFlow(mid, cc) and + readSetEx(mid, c, node) + ) + } + + /** + * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. + */ + pragma[assume_small_delta] + pragma[nomagic] + private predicate fwdFlowConsCand(Content c) { + exists(NodeEx mid, NodeEx node, TypedContent tc | + not fullBarrier(node) and + useFieldFlow() and + fwdFlow(mid, _) and + storeEx(mid, tc, node, _) and + c = tc.getContent() + ) + } + + /** + * Holds if `cs` may be interpreted in a read as the target of some store + * into `c`, in the flow covered by `fwdFlow`. + */ + pragma[nomagic] + private predicate fwdFlowConsCandSet(ContentSet cs, Content c) { + fwdFlowConsCand(c) and + c = cs.getAReadContent() + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc) { + exists(RetNodeEx ret | + fwdFlow(ret, cc) and + ret.getReturnPosition() = pos + ) + } + + // inline to reduce the number of iterations + pragma[inline] + private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc) and + viableReturnPosOutEx(call, pos, out) and + not fullBarrier(out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out) { + fwdFlowOut(call, out, true) + } + + private predicate stateStepFwd(FlowState state1, FlowState state2) { + exists(NodeEx node1 | + additionalLocalStateStep(node1, state1, _, state2) or + additionalJumpStateStep(node1, state1, _, state2) + | + fwdFlow(node1) + ) + } + + private predicate fwdFlowState(FlowState state) { + sourceNode(_, state) + or + exists(FlowState state0 | + fwdFlowState(state0) and + stateStepFwd(state0, state) + ) + } + + /** + * Holds if `node` is part of a path from a source to a sink. + * + * The Boolean `toReturn` records whether the node must be returned from + * the enclosing callable in order to reach a sink. + */ + pragma[nomagic] + private predicate revFlow(NodeEx node, boolean toReturn) { + revFlow0(node, toReturn) and + fwdFlow(node) + } + + pragma[nomagic] + private predicate revFlow0(NodeEx node, boolean toReturn) { + exists(FlowState state | + fwdFlow(node) and + sinkNode(node, state) and + fwdFlowState(state) and + if hasSinkCallCtx() then toReturn = true else toReturn = false ) or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStep(node, mid, config) and - revFlow(pragma[only_bind_into](mid), state, _, _, nil, pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil + exists(NodeEx mid | revFlow(mid, toReturn) | + localFlowStepEx(node, mid) or + additionalLocalFlowStep(node, mid) or + additionalLocalStateStep(node, _, mid, _) ) or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStateStep(node, state, mid, state0, config) and - revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil, - pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil + exists(NodeEx mid | revFlow(mid, _) and toReturn = false | + jumpStepEx(node, mid) or + additionalJumpStep(node, mid) or + additionalJumpStateStep(node, _, mid, _) ) or // store - exists(Ap ap0, Content c | - revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp, config) and - revFlowConsCand(ap0, c, ap, config) + exists(Content c | + revFlowStore(c, node, toReturn) and + revFlowConsCand(c) ) or // read - exists(NodeEx mid, Ap ap0 | - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - readStepFwd(node, ap, _, mid, ap0, config) + exists(NodeEx mid, ContentSet c | + readSetEx(node, c, mid) and + fwdFlowConsCandSet(c, _) and + revFlow(mid, toReturn) ) or // flow into a callable - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, state, TReturnCtxNone(), returnAp, ap, config) and - flowIntoCallAp(_, node, p, allowsFieldFlow, ap, config) and - (if allowsFieldFlow = false then ap instanceof ApNil else any()) and - returnCtx = TReturnCtxNone() - ) - or - // flow through a callable - exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, node, p, _, ap, innerReturnAp, config) - ) + revFlowIn(_, node, false) and + toReturn = false or // flow out of a callable exists(ReturnPosition pos | - revFlowOut(_, node, pos, state, _, _, ap, config) and - if returnFlowsThrough(node, pos, state, _, _, _, ap, config) - then ( - returnCtx = TReturnCtxMaybeFlowThrough(pos) and - returnAp = apSome(ap) - ) else ( - returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() - ) + revFlowOut(pos) and + node.(RetNodeEx).getReturnPosition() = pos and + toReturn = true + ) + or + // flow through a callable + exists(DataFlowCall call | + revFlowInToReturn(call, node) and + revFlowIsReturned(call, toReturn) ) - } - - pragma[nomagic] - private predicate revFlowStore( - Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, - ReturnCtx returnCtx, ApOption returnAp, Configuration config - ) { - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - storeStepFwd(node, ap, tc, mid, ap0, config) and - tc.getContent() = c } /** - * Holds if reverse flow with access path `tail` reaches a read of `c` - * resulting in access path `cons`. + * Holds if `c` is the target of a read in the flow covered by `revFlow`. */ pragma[nomagic] - private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(NodeEx mid, Ap tail0 | - revFlow(mid, _, _, _, tail, config) and - tail = pragma[only_bind_into](tail0) and - readStepFwd(_, cons, c, mid, tail0, config) + private predicate revFlowConsCand(Content c) { + exists(NodeEx mid, NodeEx node, ContentSet cs | + fwdFlow(node) and + readSetEx(node, cs, mid) and + fwdFlowConsCandSet(cs, c) and + revFlow(pragma[only_bind_into](mid), _) ) } pragma[nomagic] - private predicate revFlowOut( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, - ApOption returnAp, Ap ap, Configuration config + private predicate revFlowStore(Content c, NodeEx node, boolean toReturn) { + exists(NodeEx mid, TypedContent tc | + revFlow(mid, toReturn) and + fwdFlowConsCand(c) and + storeEx(node, tc, mid, _) and + c = tc.getContent() + ) + } + + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + pragma[nomagic] + additional predicate revFlowIsReadAndStored(Content c) { + revFlowConsCand(c) and + revFlowStore(c, _, _) + } + + pragma[nomagic] + additional predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, NodeEx out ) { - exists(NodeEx out, boolean allowsFieldFlow | - revFlow(out, state, returnCtx, returnAp, ap, config) and - flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() + fwdFlowReturnPosition(pos, _) and + viableReturnPosOutEx(call, pos, out) + } + + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos) { + exists(NodeEx out | + revFlow(out, _) and + viableReturnPosOutNodeCandFwd1(_, pos, out) ) } pragma[nomagic] - private predicate revFlowParamToReturn( - ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap, Configuration config - ) { - revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), - pragma[only_bind_into](ap), pragma[only_bind_into](config)) and - parameterFlowThroughAllowed(p, pos.getKind()) and - PrevStage::parameterMayFlowThrough(p, getApprox(ap), config) + additional predicate viableParamArgNodeCandFwd1(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { + fwdFlowIn(call, arg, _, p) + } + + // inline to reduce the number of iterations + pragma[inline] + private predicate revFlowIn(DataFlowCall call, ArgNodeEx arg, boolean toReturn) { + exists(ParamNodeEx p | + revFlow(p, toReturn) and + viableParamArgNodeCandFwd1(call, p, arg) + ) } pragma[nomagic] - private predicate revFlowThrough( - DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, - ApOption returnAp, Ap ap, Ap innerReturnAp, Configuration config - ) { - revFlowParamToReturn(p, state, pos, innerReturnAp, ap, config) and - revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp, config) + private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg) { + revFlowIn(call, arg, true) } /** @@ -1866,896 +766,1704 @@ private module MkStage { * reaching an argument of `call`. */ pragma[nomagic] - private predicate revFlowIsReturned( - DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap, - Configuration config - ) { - exists(RetNodeEx ret, FlowState state, CcCall ccc | - revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap, config) and - returnFlowsThrough(ret, pos, state, ccc, _, _, ap, config) and - matchesCall(ccc, call) + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn) { + exists(NodeEx out | + revFlow(out, toReturn) and + fwdFlowOutFromArg(call, out) + ) + } + + private predicate stateStepRev(FlowState state1, FlowState state2) { + exists(NodeEx node1, NodeEx node2 | + additionalLocalStateStep(node1, state1, node2, state2) or + additionalJumpStateStep(node1, state1, node2, state2) + | + revFlow(node1, _) and + revFlow(node2, _) and + fwdFlowState(state1) and + fwdFlowState(state2) + ) + } + + pragma[nomagic] + additional predicate revFlowState(FlowState state) { + exists(NodeEx node | + sinkNode(node, state) and + revFlow(node, _) and + fwdFlowState(state) + ) + or + exists(FlowState state0 | + revFlowState(state0) and + stateStepRev(state, state0) ) } pragma[nomagic] predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType ) { - exists(Ap ap2, Content c | - PrevStage::storeStepCand(node1, _, tc, node2, contentType, config) and - revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _, config) and - revFlowConsCand(ap2, c, ap1, config) - ) - } - - predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(Ap ap1, Ap ap2 | - revFlow(node2, _, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and - readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _, - pragma[only_bind_into](config)) - ) - } - - additional predicate revFlow(NodeEx node, FlowState state, Configuration config) { - revFlow(node, state, _, _, _, config) - } - - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, _, _, ap, config) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, _, _, _, ap, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, Configuration config) { - revFlow(node, _, _, _, _, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, ap, config) - } - - private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepFwd(_, ap, tc, _, _, config) - } - - private predicate revConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepCand(_, ap, tc, _, _, config) - } - - private predicate validAp(Ap ap, Configuration config) { - revFlow(_, _, _, _, ap, config) and ap instanceof ApNil - or - exists(TypedContent head, Ap tail | - consCand(head, tail, config) and - ap = apCons(head, tail) - ) - } - - additional predicate consCand(TypedContent tc, Ap ap, Configuration config) { - revConsCand(tc, ap, config) and - validAp(ap, config) - } - - pragma[nomagic] - private predicate parameterFlowsThroughRev( - ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp, Configuration config - ) { - revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap, config) and - parameterFlowThroughAllowed(p, pos.getKind()) - } - - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(ReturnPosition pos | - returnFlowsThrough(_, pos, _, _, p, ap, _, config) and - parameterFlowsThroughRev(p, ap, pos, _, config) + exists(Content c | + revFlowIsReadAndStored(c) and + revFlow(node2) and + storeEx(node1, tc, node2, contentType) and + c = tc.getContent() and + exists(ap1) ) } pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - exists(ParamNodeEx p, ReturnPosition pos | - returnFlowsThrough(ret, pos, _, _, p, argAp, ap, config) and - parameterFlowsThroughRev(p, argAp, pos, ap, config) and - kind = pos.getKind() + predicate readStepCand(NodeEx n1, Content c, NodeEx n2) { + revFlowIsReadAndStored(c) and + read(n1, c, n2) and + revFlow(n2) + } + + pragma[nomagic] + predicate revFlow(NodeEx node) { revFlow(node, _) } + + pragma[nomagic] + predicate revFlowAp(NodeEx node, Ap ap) { + revFlow(node) and + exists(ap) + } + + bindingset[node, state] + predicate revFlow(NodeEx node, FlowState state, Ap ap) { + revFlow(node, _) and + exists(state) and + exists(ap) + } + + private predicate throughFlowNodeCand(NodeEx node) { + revFlow(node, true) and + fwdFlow(node, true) and + not inBarrier(node) and + not outBarrier(node) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand(DataFlowCallable callable, ReturnKindExt kind) { + exists(RetNodeEx ret | + throughFlowNodeCand(ret) and + callable = ret.getEnclosingCallable() and + kind = ret.getKind() + ) + } + + /** + * Holds if flow may enter through `p` and reach a return node making `p` a + * candidate for the origin of a summary. + */ + pragma[nomagic] + predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap) { + exists(DataFlowCallable c, ReturnKindExt kind | + throughFlowNodeCand(p) and + returnFlowCallableNodeCand(c, kind) and + p.getEnclosingCallable() = c and + exists(ap) and + parameterFlowThroughAllowed(p, kind) ) } pragma[nomagic] - private predicate revFlowThroughArg( - DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, - Ap ap, Configuration config - ) { - exists(ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp, config) - ) + predicate returnMayFlowThrough(RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind) { + throughFlowNodeCand(ret) and + kind = ret.getKind() and + exists(argAp) and + exists(ap) } pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | - revFlow(arg, state, returnCtx, returnAp, ap, config) and - revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap, config) + predicate callMayFlowThroughRev(DataFlowCall call) { + exists(ArgNodeEx arg, boolean toReturn | + revFlow(arg, toReturn) and + revFlowInToReturn(call, arg) and + revFlowIsReturned(call, toReturn) ) } additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config + boolean fwd, int nodes, int fields, int conscand, int states, int tuples ) { fwd = true and - nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, config)) and - fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and - states = count(FlowState state | fwdFlow(_, state, _, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | - fwdFlow(n, state, cc, summaryCtx, argAp, ap, config) - ) + nodes = count(NodeEx node | fwdFlow(node)) and + fields = count(Content f0 | fwdFlowConsCand(f0)) and + conscand = -1 and + states = count(FlowState state | fwdFlowState(state)) and + tuples = count(NodeEx n, boolean b | fwdFlow(n, b)) or fwd = false and - nodes = count(NodeEx node | revFlow(node, _, _, _, _, config)) and - fields = count(TypedContent f0 | consCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and - states = count(FlowState state | revFlow(_, state, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | - revFlow(n, state, returnCtx, retAp, ap, config) - ) + nodes = count(NodeEx node | revFlow(node, _)) and + fields = count(Content f0 | revFlowConsCand(f0)) and + conscand = -1 and + states = count(FlowState state | revFlowState(state)) and + tuples = count(NodeEx n, boolean b | revFlow(n, b)) } - /* End: Stage logic. */ - } -} - -private module BooleanCallContext { - class Cc extends boolean { - Cc() { this in [true, false] } + /* End: Stage 1 logic. */ } - class CcCall extends Cc { - CcCall() { this = true } + pragma[noinline] + private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2) { + Stage1::revFlow(node2) and + localFlowStepEx(node1, node2) } - /** Holds if the call context may be `call`. */ - predicate matchesCall(CcCall cc, DataFlowCall call) { any() } - - class CcNoCall extends Cc { - CcNoCall() { this = false } + pragma[noinline] + private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2) { + Stage1::revFlow(node2) and + additionalLocalFlowStep(node1, node2) } - Cc ccNone() { result = false } + pragma[nomagic] + private predicate viableReturnPosOutNodeCand1(DataFlowCall call, ReturnPosition pos, NodeEx out) { + Stage1::revFlow(out) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out) + } - CcCall ccSomeCall() { result = true } + /** + * Holds if data can flow out of `call` from `ret` to `out`, either + * through a `ReturnNode` or through an argument that has been mutated, and + * that this step is part of a path from a source to a sink. + */ + pragma[nomagic] + private predicate flowOutOfCallNodeCand1( + DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out + ) { + exists(ReturnPosition pos | + viableReturnPosOutNodeCand1(call, pos, out) and + pos = ret.getReturnPosition() and + kind = pos.getKind() and + Stage1::revFlow(ret) and + not outBarrier(ret) and + not inBarrier(out) + ) + } - class LocalCc = Unit; + pragma[nomagic] + private predicate viableParamArgNodeCand1(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { + Stage1::viableParamArgNodeCandFwd1(call, p, arg) and + Stage1::revFlow(arg) + } - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } + /** + * Holds if data can flow into `call` and that this step is part of a + * path from a source to a sink. + */ + pragma[nomagic] + private predicate flowIntoCallNodeCand1(DataFlowCall call, ArgNodeEx arg, ParamNodeEx p) { + viableParamArgNodeCand1(call, p, arg) and + Stage1::revFlow(p) and + not outBarrier(arg) and + not inBarrier(p) + } - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + /** + * Gets the amount of forward branching on the origin of a cross-call path + * edge in the graph of paths between sources and sinks that ignores call + * contexts. + */ + pragma[nomagic] + private int branch(NodeEx n1) { + result = + strictcount(NodeEx n | flowOutOfCallNodeCand1(_, n1, _, n) or flowIntoCallNodeCand1(_, n1, n)) + } - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } -} + /** + * Gets the amount of backward branching on the target of a cross-call path + * edge in the graph of paths between sources and sinks that ignores call + * contexts. + */ + pragma[nomagic] + private int join(NodeEx n2) { + result = + strictcount(NodeEx n | flowOutOfCallNodeCand1(_, n, _, n2) or flowIntoCallNodeCand1(_, n, n2)) + } -private module Level1CallContext { - class Cc = CallContext; + /** + * Holds if data can flow out of `call` from `ret` to `out`, either + * through a `ReturnNode` or through an argument that has been mutated, and + * that this step is part of a path from a source to a sink. The + * `allowsFieldFlow` flag indicates whether the branching is within the limit + * specified by the configuration. + */ + pragma[nomagic] + private predicate flowOutOfCallNodeCand1( + DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow + ) { + flowOutOfCallNodeCand1(call, ret, kind, out) and + exists(int b, int j | + b = branch(ret) and + j = join(out) and + if b.minimum(j) <= Config::fieldFlowBranchLimit() + then allowsFieldFlow = true + else allowsFieldFlow = false + ) + } - class CcCall = CallContextCall; + /** + * Holds if data can flow into `call` and that this step is part of a + * path from a source to a sink. The `allowsFieldFlow` flag indicates whether + * the branching is within the limit specified by the configuration. + */ + pragma[nomagic] + private predicate flowIntoCallNodeCand1( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow + ) { + flowIntoCallNodeCand1(call, arg, p) and + exists(int b, int j | + b = branch(arg) and + j = join(p) and + if b.minimum(j) <= Config::fieldFlowBranchLimit() + then allowsFieldFlow = true + else allowsFieldFlow = false + ) + } - pragma[inline] - predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } + private signature module StageSig { + class Ap; - class CcNoCall = CallContextNoCall; + predicate revFlow(NodeEx node); - Cc ccNone() { result instanceof CallContextAny } + predicate revFlowAp(NodeEx node, Ap ap); - CcCall ccSomeCall() { result instanceof CallContextSomeCall } + bindingset[node, state] + predicate revFlow(NodeEx node, FlowState state, Ap ap); + + predicate callMayFlowThroughRev(DataFlowCall call); + + predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap); + + predicate returnMayFlowThrough(RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind); + + predicate storeStepCand( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType + ); + + predicate readStepCand(NodeEx n1, Content c, NodeEx n2); + } + + private module MkStage { + class ApApprox = PrevStage::Ap; + + signature module StageParam { + class Ap; + + class ApNil extends Ap; + + bindingset[result, ap] + ApApprox getApprox(Ap ap); + + ApNil getApNil(NodeEx node); + + bindingset[tc, tail] + Ap apCons(TypedContent tc, Ap tail); + + /** + * An approximation of `Content` that corresponds to the precision level of + * `Ap`, such that the mappings from both `Ap` and `Content` to this type + * are functional. + */ + class ApHeadContent; + + ApHeadContent getHeadContent(Ap ap); + + ApHeadContent projectToHeadContent(Content c); + + class ApOption; + + ApOption apNone(); + + ApOption apSome(Ap ap); + + class Cc; + + class CcCall extends Cc; + + // TODO: member predicate on CcCall + predicate matchesCall(CcCall cc, DataFlowCall call); + + class CcNoCall extends Cc; + + Cc ccNone(); + + CcCall ccSomeCall(); + + class LocalCc; + + bindingset[call, c, outercc] + CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); + + bindingset[call, c, innercc] + CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); + + bindingset[node, cc] + LocalCc getLocalCc(NodeEx node, Cc cc); + + bindingset[node1, state1] + bindingset[node2, state2] + predicate localStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + ApNil ap, LocalCc lcc + ); + + predicate flowOutOfCall( + DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow + ); + + predicate flowIntoCall( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow + ); + + bindingset[node, state, ap] + predicate filter(NodeEx node, FlowState state, Ap ap); + + bindingset[ap, contentType] + predicate typecheckStore(Ap ap, DataFlowType contentType); + } + + module Stage implements StageSig { + import Param + + /* Begin: Stage logic. */ + // use an alias as a workaround for bad functionality-induced joins + pragma[nomagic] + private predicate revFlowApAlias(NodeEx node, ApApprox apa) { + PrevStage::revFlowAp(node, apa) + } + + pragma[nomagic] + private predicate flowIntoCallApa( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa + ) { + flowIntoCall(call, arg, p, allowsFieldFlow) and + PrevStage::revFlowAp(p, pragma[only_bind_into](apa)) and + revFlowApAlias(arg, pragma[only_bind_into](apa)) + } + + pragma[nomagic] + private predicate flowOutOfCallApa( + DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, + ApApprox apa + ) { + flowOutOfCall(call, ret, kind, out, allowsFieldFlow) and + PrevStage::revFlowAp(out, pragma[only_bind_into](apa)) and + revFlowApAlias(ret, pragma[only_bind_into](apa)) + } + + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, + ApApprox argApa, ApApprox apa + ) { + exists(ReturnKindExt kind | + flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa) and + PrevStage::callMayFlowThroughRev(call) and + PrevStage::returnMayFlowThrough(ret, argApa, apa, kind) and + matchesCall(ccc, call) + ) + } + + /** + * Holds if `node` is reachable with access path `ap` from a source. + * + * The call context `cc` records whether the node is reached through an + * argument in a call, and if so, `summaryCtx` and `argAp` record the + * corresponding parameter position and access path of that argument, respectively. + */ + pragma[nomagic] + additional predicate fwdFlow( + NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, + ApApprox apa + ) { + fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa) and + PrevStage::revFlow(node, state, apa) and + filter(node, state, ap) + } + + pragma[inline] + additional predicate fwdFlow( + NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap + ) { + fwdFlow(node, state, cc, summaryCtx, argAp, ap, _) + } + + pragma[assume_small_delta] + pragma[nomagic] + private predicate fwdFlow0( + NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, + ApApprox apa + ) { + sourceNode(node, state) and + (if hasSourceCallCtx() then cc = ccSomeCall() else cc = ccNone()) and + argAp = apNone() and + summaryCtx = TParamNodeNone() and + ap = getApNil(node) and + apa = getApprox(ap) + or + exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | + fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0) and + localCc = getLocalCc(mid, cc) + | + localStep(mid, state0, node, state, true, _, localCc) and + ap = ap0 and + apa = apa0 + or + localStep(mid, state0, node, state, false, ap, localCc) and + ap0 instanceof ApNil and + apa = getApprox(ap) + ) + or + exists(NodeEx mid | + fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa) and + jumpStepEx(mid, node) and + cc = ccNone() and + summaryCtx = TParamNodeNone() and + argAp = apNone() + ) + or + exists(NodeEx mid, ApNil nil | + fwdFlow(mid, state, _, _, _, nil) and + additionalJumpStep(mid, node) and + cc = ccNone() and + summaryCtx = TParamNodeNone() and + argAp = apNone() and + ap = getApNil(node) and + apa = getApprox(ap) + ) + or + exists(NodeEx mid, FlowState state0, ApNil nil | + fwdFlow(mid, state0, _, _, _, nil) and + additionalJumpStateStep(mid, state0, node, state) and + cc = ccNone() and + summaryCtx = TParamNodeNone() and + argAp = apNone() and + ap = getApNil(node) and + apa = getApprox(ap) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp) and + ap = apCons(tc, ap0) and + apa = getApprox(ap) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp) and + fwdFlowConsCand(ap0, c, ap) and + apa = getApprox(ap) + ) + or + // flow into a callable + fwdFlowIn(_, node, state, _, cc, _, _, ap, apa) and + if PrevStage::parameterMayFlowThrough(node, apa) + then ( + summaryCtx = TParamNodeSome(node.asNode()) and + argAp = apSome(ap) + ) else ( + summaryCtx = TParamNodeNone() and argAp = apNone() + ) + or + // flow out of a callable + exists( + DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | + fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa) and + flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa) and + inner = ret.getEnclosingCallable() and + cc = getCallContextReturn(inner, call, innercc) and + if allowsFieldFlow = false then ap instanceof ApNil else any() + ) + or + // flow through a callable + exists( + DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, + ApApprox innerArgApa + | + fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa) and + flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa) and + if allowsFieldFlow = false then ap instanceof ApNil else any() + ) + } + + pragma[nomagic] + private predicate fwdFlowStore( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, + ParamNodeOption summaryCtx, ApOption argAp + ) { + exists(DataFlowType contentType, ApApprox apa1 | + fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1) and + PrevStage::storeStepCand(node1, apa1, tc, node2, contentType) and + typecheckStore(ap1, contentType) + ) + } + + /** + * Holds if forward flow with access path `tail` reaches a store of `c` + * resulting in access path `cons`. + */ + pragma[nomagic] + private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail) { + exists(TypedContent tc | + fwdFlowStore(_, tail, tc, _, _, _, _, _) and + tc.getContent() = c and + cons = apCons(tc, tail) + ) + } + + pragma[nomagic] + private predicate readStepCand(NodeEx node1, ApHeadContent apc, Content c, NodeEx node2) { + PrevStage::readStepCand(node1, c, node2) and + apc = projectToHeadContent(c) + } + + bindingset[node1, apc] + pragma[inline_late] + private predicate readStepCand0(NodeEx node1, ApHeadContent apc, Content c, NodeEx node2) { + readStepCand(node1, apc, c, node2) + } + + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, + ParamNodeOption summaryCtx, ApOption argAp + ) { + exists(ApHeadContent apc | + fwdFlow(node1, state, cc, summaryCtx, argAp, ap) and + apc = getHeadContent(ap) and + readStepCand0(node1, apc, c, node2) + ) + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, + ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa + ) { + exists(ArgNodeEx arg, boolean allowsFieldFlow | + fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa) and + flowIntoCallApa(call, arg, p, allowsFieldFlow, apa) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and + if allowsFieldFlow = false then ap instanceof ApNil else any() + ) + } + + pragma[nomagic] + private predicate fwdFlowRetFromArg( + RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, + ApApprox argApa, Ap ap, ApApprox apa + ) { + exists(ReturnKindExt kind | + fwdFlow(pragma[only_bind_into](ret), state, ccc, + TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), + pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa)) and + kind = ret.getKind() and + parameterFlowThroughAllowed(summaryCtx, kind) and + argApa = getApprox(argAp) and + PrevStage::returnMayFlowThrough(ret, argApa, apa, kind) + ) + } + + pragma[inline] + private predicate fwdFlowThrough0( + DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, + ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, + Ap innerArgAp, ApApprox innerArgApa + ) { + fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa) and + fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp) + } + + pragma[nomagic] + private predicate fwdFlowThrough( + DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, + ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa + ) { + fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa) + } + + /** + * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` + * and data might flow through the target callable and back out at `call`. + */ + pragma[nomagic] + private predicate fwdFlowIsEntered( + DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, + ParamNodeEx p, Ap ap + ) { + exists(ApApprox apa | + fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, + pragma[only_bind_into](apa)) and + PrevStage::parameterMayFlowThrough(p, apa) and + PrevStage::callMayFlowThroughRev(call) + ) + } + + pragma[nomagic] + private predicate storeStepFwd(NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2) { + fwdFlowStore(node1, ap1, tc, node2, _, _, _, _) and + ap2 = apCons(tc, ap1) and + fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _) + } + + private predicate readStepFwd(NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2) { + fwdFlowRead(ap1, c, n1, n2, _, _, _, _) and + fwdFlowConsCand(ap1, c, ap2) + } + + pragma[nomagic] + private predicate returnFlowsThrough0( + DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, + ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa + ) { + fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, + innerArgApa) + } + + pragma[nomagic] + private predicate returnFlowsThrough( + RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, + Ap ap + ) { + exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | + returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa) and + flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa) and + pos = ret.getReturnPosition() and + if allowsFieldFlow = false then ap instanceof ApNil else any() + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap + ) { + exists(ApApprox argApa | + flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), + allowsFieldFlow, argApa) and + fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa) and + returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap) and + if allowsFieldFlow = false then argAp instanceof ApNil else any() + ) + } + + pragma[nomagic] + private predicate flowIntoCallAp( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap + ) { + exists(ApApprox apa | + flowIntoCallApa(call, arg, p, allowsFieldFlow, apa) and + fwdFlow(arg, _, _, _, _, ap, apa) + ) + } + + pragma[nomagic] + private predicate flowOutOfCallAp( + DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, + Ap ap + ) { + exists(ApApprox apa | + flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa) and + fwdFlow(ret, _, _, _, _, ap, apa) and + pos = ret.getReturnPosition() + ) + } + + /** + * Holds if `node` with access path `ap` is part of a path from a source to a + * sink. + * + * The parameter `returnCtx` records whether (and how) the node must be returned + * from the enclosing callable in order to reach a sink, and if so, `returnAp` + * records the access path of the returned value. + */ + pragma[nomagic] + additional predicate revFlow( + NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap + ) { + revFlow0(node, state, returnCtx, returnAp, ap) and + fwdFlow(node, state, _, _, _, ap) + } + + pragma[nomagic] + private predicate revFlow0( + NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap + ) { + fwdFlow(node, state, _, _, _, ap) and + sinkNode(node, state) and + ( + if hasSinkCallCtx() + then returnCtx = TReturnCtxNoFlowThrough() + else returnCtx = TReturnCtxNone() + ) and + returnAp = apNone() and + ap instanceof ApNil + or + exists(NodeEx mid, FlowState state0 | + localStep(node, state, mid, state0, true, _, _) and + revFlow(mid, state0, returnCtx, returnAp, ap) + ) + or + exists(NodeEx mid, FlowState state0, ApNil nil | + fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap) and + localStep(node, pragma[only_bind_into](state), mid, state0, false, _, _) and + revFlow(mid, state0, returnCtx, returnAp, nil) and + ap instanceof ApNil + ) + or + exists(NodeEx mid | + jumpStepEx(node, mid) and + revFlow(mid, state, _, _, ap) and + returnCtx = TReturnCtxNone() and + returnAp = apNone() + ) + or + exists(NodeEx mid, ApNil nil | + fwdFlow(node, _, _, _, _, ap) and + additionalJumpStep(node, mid) and + revFlow(pragma[only_bind_into](mid), state, _, _, nil) and + returnCtx = TReturnCtxNone() and + returnAp = apNone() and + ap instanceof ApNil + ) + or + exists(NodeEx mid, FlowState state0, ApNil nil | + fwdFlow(node, _, _, _, _, ap) and + additionalJumpStateStep(node, state, mid, state0) and + revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil) and + returnCtx = TReturnCtxNone() and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp) and + revFlowConsCand(ap0, c, ap) + ) + or + // read + exists(NodeEx mid, Ap ap0 | + revFlow(mid, state, returnCtx, returnAp, ap0) and + readStepFwd(node, ap, _, mid, ap0) + ) + or + // flow into a callable + exists(ParamNodeEx p, boolean allowsFieldFlow | + revFlow(p, state, TReturnCtxNone(), returnAp, ap) and + flowIntoCallAp(_, node, p, allowsFieldFlow, ap) and + (if allowsFieldFlow = false then ap instanceof ApNil else any()) and + returnCtx = TReturnCtxNone() + ) + or + // flow through a callable + exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | + revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp) and + flowThroughIntoCall(call, node, p, _, ap, innerReturnAp) + ) + or + // flow out of a callable + exists(ReturnPosition pos | + revFlowOut(_, node, pos, state, _, _, ap) and + if returnFlowsThrough(node, pos, state, _, _, _, ap) + then ( + returnCtx = TReturnCtxMaybeFlowThrough(pos) and + returnAp = apSome(ap) + ) else ( + returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() + ) + ) + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, + ReturnCtx returnCtx, ApOption returnAp + ) { + revFlow(mid, state, returnCtx, returnAp, ap0) and + storeStepFwd(node, ap, tc, mid, ap0) and + tc.getContent() = c + } + + /** + * Holds if reverse flow with access path `tail` reaches a read of `c` + * resulting in access path `cons`. + */ + pragma[nomagic] + private predicate revFlowConsCand(Ap cons, Content c, Ap tail) { + exists(NodeEx mid, Ap tail0 | + revFlow(mid, _, _, _, tail) and + tail = pragma[only_bind_into](tail0) and + readStepFwd(_, cons, c, mid, tail0) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, + ApOption returnAp, Ap ap + ) { + exists(NodeEx out, boolean allowsFieldFlow | + revFlow(out, state, returnCtx, returnAp, ap) and + flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap) and + if allowsFieldFlow = false then ap instanceof ApNil else any() + ) + } + + pragma[nomagic] + private predicate revFlowParamToReturn( + ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap + ) { + revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), + pragma[only_bind_into](ap)) and + parameterFlowThroughAllowed(p, pos.getKind()) and + PrevStage::parameterMayFlowThrough(p, getApprox(ap)) + } + + pragma[nomagic] + private predicate revFlowThrough( + DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, + ApOption returnAp, Ap ap, Ap innerReturnAp + ) { + revFlowParamToReturn(p, state, pos, innerReturnAp, ap) and + revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. + */ + pragma[nomagic] + private predicate revFlowIsReturned( + DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap + ) { + exists(RetNodeEx ret, FlowState state, CcCall ccc | + revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap) and + returnFlowsThrough(ret, pos, state, ccc, _, _, ap) and + matchesCall(ccc, call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType + ) { + exists(Ap ap2, Content c | + PrevStage::storeStepCand(node1, _, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _) and + revFlowConsCand(ap2, c, ap1) + ) + } + + predicate readStepCand(NodeEx node1, Content c, NodeEx node2) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, _, pragma[only_bind_into](ap2)) and + readStepFwd(node1, ap1, c, node2, ap2) and + revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _) + ) + } + + additional predicate revFlow(NodeEx node, FlowState state) { revFlow(node, state, _, _, _) } + + predicate revFlow(NodeEx node, FlowState state, Ap ap) { revFlow(node, state, _, _, ap) } + + pragma[nomagic] + predicate revFlow(NodeEx node) { revFlow(node, _, _, _, _) } + + pragma[nomagic] + predicate revFlowAp(NodeEx node, Ap ap) { revFlow(node, _, _, _, ap) } + + // use an alias as a workaround for bad functionality-induced joins + pragma[nomagic] + additional predicate revFlowAlias(NodeEx node) { revFlow(node, _, _, _, _) } + + // use an alias as a workaround for bad functionality-induced joins + pragma[nomagic] + additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap) { + revFlow(node, state, ap) + } + + private predicate fwdConsCand(TypedContent tc, Ap ap) { storeStepFwd(_, ap, tc, _, _) } + + private predicate revConsCand(TypedContent tc, Ap ap) { storeStepCand(_, ap, tc, _, _) } + + private predicate validAp(Ap ap) { + revFlow(_, _, _, _, ap) and ap instanceof ApNil + or + exists(TypedContent head, Ap tail | + consCand(head, tail) and + ap = apCons(head, tail) + ) + } + + additional predicate consCand(TypedContent tc, Ap ap) { + revConsCand(tc, ap) and + validAp(ap) + } + + pragma[nomagic] + private predicate parameterFlowsThroughRev( + ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp + ) { + revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap) and + parameterFlowThroughAllowed(p, pos.getKind()) + } + + pragma[nomagic] + predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap) { + exists(ReturnPosition pos | + returnFlowsThrough(_, pos, _, _, p, ap, _) and + parameterFlowsThroughRev(p, ap, pos, _) + ) + } + + pragma[nomagic] + predicate returnMayFlowThrough(RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind) { + exists(ParamNodeEx p, ReturnPosition pos | + returnFlowsThrough(ret, pos, _, _, p, argAp, ap) and + parameterFlowsThroughRev(p, argAp, pos, ap) and + kind = pos.getKind() + ) + } + + pragma[nomagic] + private predicate revFlowThroughArg( + DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, + Ap ap + ) { + exists(ParamNodeEx p, Ap innerReturnAp | + revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp) and + flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp) + ) + } + + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call) { + exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | + revFlow(arg, state, returnCtx, returnAp, ap) and + revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap) + ) + } + + additional predicate stats( + boolean fwd, int nodes, int fields, int conscand, int states, int tuples + ) { + fwd = true and + nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap)) and + states = count(FlowState state | fwdFlow(_, state, _, _, _, _)) and + tuples = + count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | + fwdFlow(n, state, cc, summaryCtx, argAp, ap) + ) + or + fwd = false and + nodes = count(NodeEx node | revFlow(node, _, _, _, _)) and + fields = count(TypedContent f0 | consCand(f0, _)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap)) and + states = count(FlowState state | revFlow(_, state, _, _, _)) and + tuples = + count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | + revFlow(n, state, returnCtx, retAp, ap) + ) + } + /* End: Stage logic. */ + } + } + + private module BooleanCallContext { + class Cc extends boolean { + Cc() { this in [true, false] } + } + + class CcCall extends Cc { + CcCall() { this = true } + } + + /** Holds if the call context may be `call`. */ + predicate matchesCall(CcCall cc, DataFlowCall call) { any() } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccNone() { result = false } + + CcCall ccSomeCall() { result = true } - module NoLocalCallContext { class LocalCc = Unit; bindingset[node, cc] LocalCc getLocalCc(NodeEx node, Cc cc) { any() } bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSiteDispatch(call, c) - then result = TSpecificCall(call) - else result = TSomeCall() + CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c, innercc] + CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } + } + + private module Level1CallContext { + class Cc = CallContext; + + class CcCall = CallContextCall; + + pragma[inline] + predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } + + class CcNoCall = CallContextNoCall; + + Cc ccNone() { result instanceof CallContextAny } + + CcCall ccSomeCall() { result instanceof CallContextSomeCall } + + module NoLocalCallContext { + class LocalCc = Unit; + + bindingset[node, cc] + LocalCc getLocalCc(NodeEx node, Cc cc) { any() } + + bindingset[call, c, outercc] + CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + checkCallContextCall(outercc, call, c) and + if recordDataFlowCallSiteDispatch(call, c) + then result = TSpecificCall(call) + else result = TSomeCall() + } + } + + module LocalCallContext { + class LocalCc = LocalCallContext; + + bindingset[node, cc] + LocalCc getLocalCc(NodeEx node, Cc cc) { + result = + getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), + node.getEnclosingCallable()) + } + + bindingset[call, c, outercc] + CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + checkCallContextCall(outercc, call, c) and + if recordDataFlowCallSite(call, c) + then result = TSpecificCall(call) + else result = TSomeCall() + } + } + + bindingset[call, c, innercc] + CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { + checkCallContextReturn(innercc, c, call) and + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() } } - module LocalCallContext { - class LocalCc = LocalCallContext; + private module Stage2Param implements MkStage::StageParam { + private module PrevStage = Stage1; - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { - result = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - node.getEnclosingCallable()) + class Ap extends boolean { + Ap() { this in [true, false] } } - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + class ApNil extends Ap { + ApNil() { this = false } } + + bindingset[result, ap] + PrevStage::Ap getApprox(Ap ap) { any() } + + ApNil getApNil(NodeEx node) { Stage1::revFlow(node) and exists(result) } + + bindingset[tc, tail] + Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + class ApHeadContent = Unit; + + pragma[inline] + ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } + + ApHeadContent projectToHeadContent(Content c) { any() } + + class ApOption = BooleanOption; + + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + + import Level1CallContext + import NoLocalCallContext + + bindingset[node1, state1] + bindingset[node2, state2] + predicate localStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + ApNil ap, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2) and + state1 = state2 + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2) and + state1 = state2 + or + preservesValue = false and + additionalLocalStateStep(node1, state1, node2, state2) + ) and + exists(ap) and + exists(lcc) + } + + predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + predicate flowIntoCall = flowIntoCallNodeCand1/4; + + pragma[nomagic] + private predicate expectsContentCand(NodeEx node) { + exists(Content c | + PrevStage::revFlow(node) and + PrevStage::revFlowIsReadAndStored(c) and + expectsContentEx(node, c) + ) + } + + bindingset[node, state, ap] + predicate filter(NodeEx node, FlowState state, Ap ap) { + PrevStage::revFlowState(state) and + exists(ap) and + not stateBarrier(node, state) and + ( + notExpectsContent(node) + or + ap = true and + expectsContentCand(node) + ) + } + + bindingset[ap, contentType] + predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } } - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { - checkCallContextReturn(innercc, c, call) and - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() + private module Stage2 implements StageSig { + import MkStage::Stage } -} - -private module Stage2Param implements MkStage::StageParam { - private module PrevStage = Stage1; - - class Ap extends boolean { - Ap() { this in [true, false] } - } - - class ApNil extends Ap { - ApNil() { this = false } - } - - bindingset[result, ap] - PrevStage::Ap getApprox(Ap ap) { any() } - - ApNil getApNil(NodeEx node) { Stage1::revFlow(node, _) and exists(result) } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } - - class ApHeadContent = Unit; - - pragma[inline] - ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } - - ApHeadContent projectToHeadContent(Content c) { any() } - - class ApOption = BooleanOption; - - ApOption apNone() { result = TBooleanNone() } - - ApOption apSome(Ap ap) { result = TBooleanSome(ap) } - - import Level1CallContext - import NoLocalCallContext - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - ( - preservesValue = true and - localFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalStateStep(node1, state1, node2, state2, config) - ) and - exists(ap) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand1/6; - - predicate flowIntoCall = flowIntoCallNodeCand1/5; pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - expectsContentEx(node, c) - ) + private predicate flowOutOfCallNodeCand2( + DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow + ) { + flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow) and + Stage2::revFlow(node2) and + Stage2::revFlowAlias(node1) } - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - PrevStage::revFlowState(state, pragma[only_bind_into](config)) and - exists(ap) and - not stateBarrier(node, state, config) and - ( - notExpectsContent(node) - or - ap = true and - expectsContentCand(node, config) - ) + pragma[nomagic] + private predicate flowIntoCallNodeCand2( + DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow + ) { + flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow) and + Stage2::revFlow(node2) and + Stage2::revFlowAlias(node1) } - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage2 implements StageSig { - import MkStage::Stage -} - -pragma[nomagic] -private predicate flowOutOfCallNodeCand2( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -pragma[nomagic] -private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -private module LocalFlowBigStep { - /** - * A node where some checking is required, and hence the big-step relation - * is not allowed to step over. - */ - private class FlowCheckNode extends NodeEx { - FlowCheckNode() { - castNode(this.asNode()) or - clearsContentCached(this.asNode(), _) or - expectsContentCached(this.asNode(), _) + private module LocalFlowBigStep { + /** + * A node where some checking is required, and hence the big-step relation + * is not allowed to step over. + */ + private class FlowCheckNode extends NodeEx { + FlowCheckNode() { + castNode(this.asNode()) or + clearsContentCached(this.asNode(), _) or + expectsContentCached(this.asNode(), _) + } } - } - /** - * Holds if `node` can be the first node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowEntry(NodeEx node, FlowState state, Configuration config) { - Stage2::revFlow(node, state, config) and - ( - sourceNode(node, state, config) + /** + * Holds if `node` can be the first node in a maximal subsequence of local + * flow steps in a dataflow path. + */ + private predicate localFlowEntry(NodeEx node, FlowState state) { + Stage2::revFlow(node, state) and + ( + sourceNode(node, state) + or + jumpStepEx(_, node) + or + additionalJumpStep(_, node) + or + additionalJumpStateStep(_, _, node, state) + or + node instanceof ParamNodeEx + or + node.asNode() instanceof OutNodeExt + or + Stage2::storeStepCand(_, _, _, node, _) + or + Stage2::readStepCand(_, _, node) + or + node instanceof FlowCheckNode + or + exists(FlowState s | + additionalLocalStateStep(_, s, node, state) and + s != state + ) + ) + } + + /** + * Holds if `node` can be the last node in a maximal subsequence of local + * flow steps in a dataflow path. + */ + private predicate localFlowExit(NodeEx node, FlowState state) { + exists(NodeEx next | Stage2::revFlow(next, state) | + jumpStepEx(node, next) or + additionalJumpStep(node, next) or + flowIntoCallNodeCand2(_, node, next, _) or + flowOutOfCallNodeCand2(_, node, _, next, _) or + Stage2::storeStepCand(node, _, _, next, _) or + Stage2::readStepCand(node, _, next) + ) or - jumpStep(_, node, config) - or - additionalJumpStep(_, node, config) - or - additionalJumpStateStep(_, _, node, state, config) - or - node instanceof ParamNodeEx - or - node.asNode() instanceof OutNodeExt - or - Stage2::storeStepCand(_, _, _, node, _, config) - or - Stage2::readStepCand(_, _, node, config) - or - node instanceof FlowCheckNode - or - exists(FlowState s | - additionalLocalStateStep(_, s, node, state, config) and + exists(NodeEx next, FlowState s | Stage2::revFlow(next, s) | + additionalJumpStateStep(node, state, next, s) + or + additionalLocalStateStep(node, state, next, s) and s != state ) - ) - } - - /** - * Holds if `node` can be the last node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowExit(NodeEx node, FlowState state, Configuration config) { - exists(NodeEx next | Stage2::revFlow(next, state, config) | - jumpStep(node, next, config) or - additionalJumpStep(node, next, config) or - flowIntoCallNodeCand2(_, node, next, _, config) or - flowOutOfCallNodeCand2(_, node, _, next, _, config) or - Stage2::storeStepCand(node, _, _, next, _, config) or - Stage2::readStepCand(node, _, next, config) - ) - or - exists(NodeEx next, FlowState s | Stage2::revFlow(next, s, config) | - additionalJumpStateStep(node, state, next, s, config) or - additionalLocalStateStep(node, state, next, s, config) and - s != state - ) - or - Stage2::revFlow(node, state, config) and - node instanceof FlowCheckNode - or - sinkNode(node, state, config) - } + Stage2::revFlow(node, state) and + node instanceof FlowCheckNode + or + sinkNode(node, state) + } - pragma[noinline] - private predicate additionalLocalFlowStepNodeCand2( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, Configuration config - ) { - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 and - Stage2::revFlow(node1, pragma[only_bind_into](state1), false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false, - pragma[only_bind_into](config)) - or - additionalLocalStateStep(node1, state1, node2, state2, config) and - Stage2::revFlow(node1, state1, false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, state2, false, pragma[only_bind_into](config)) - } + pragma[noinline] + private predicate additionalLocalFlowStepNodeCand2( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2 + ) { + additionalLocalFlowStepNodeCand1(node1, node2) and + state1 = state2 and + Stage2::revFlow(node1, pragma[only_bind_into](state1), false) and + Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false) + or + additionalLocalStateStep(node1, state1, node2, state2) and + Stage2::revFlow(node1, state1, false) and + Stage2::revFlowAlias(node2, state2, false) + } - /** - * Holds if the local path from `node1` to `node2` is a prefix of a maximal - * subsequence of local flow steps in a dataflow path. - * - * This is the transitive closure of `[additional]localFlowStep` beginning - * at `localFlowEntry`. - */ - pragma[nomagic] - private predicate localFlowStepPlus( - NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, - Configuration config, LocalCallContext cc - ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and - ( - localFlowEntry(node1, pragma[only_bind_into](state), pragma[only_bind_into](config)) and + /** + * Holds if the local path from `node1` to `node2` is a prefix of a maximal + * subsequence of local flow steps in a dataflow path. + * + * This is the transitive closure of `[additional]localFlowStep` beginning + * at `localFlowEntry`. + */ + pragma[nomagic] + private predicate localFlowStepPlus( + NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, + LocalCallContext cc + ) { + not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and ( - localFlowStepNodeCand1(node1, node2, config) and - preservesValue = true and - t = node1.getDataFlowType() and // irrelevant dummy value - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) + localFlowEntry(node1, pragma[only_bind_into](state)) and + ( + localFlowStepNodeCand1(node1, node2) and + preservesValue = true and + t = node1.getDataFlowType() and // irrelevant dummy value + Stage2::revFlow(node2, pragma[only_bind_into](state)) + or + additionalLocalFlowStepNodeCand2(node1, state, node2, state) and + preservesValue = false and + t = node2.getDataFlowType() + ) and + node1 != node2 and + cc.relevantFor(node1.getEnclosingCallable()) and + not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) or - additionalLocalFlowStepNodeCand2(node1, state, node2, state, config) and - preservesValue = false and - t = node2.getDataFlowType() - ) and - node1 != node2 and - cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) - or - exists(NodeEx mid | - localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, - pragma[only_bind_into](config), cc) and - localFlowStepNodeCand1(mid, node2, config) and - not mid instanceof FlowCheckNode and - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) + exists(NodeEx mid | + localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, cc) and + localFlowStepNodeCand1(mid, node2) and + not mid instanceof FlowCheckNode and + Stage2::revFlow(node2, pragma[only_bind_into](state)) + ) + or + exists(NodeEx mid | + localFlowStepPlus(node1, state, mid, _, _, cc) and + additionalLocalFlowStepNodeCand2(mid, state, node2, state) and + not mid instanceof FlowCheckNode and + preservesValue = false and + t = node2.getDataFlowType() + ) ) + } + + /** + * Holds if `node1` can step to `node2` in one or more local steps and this + * path can occur as a maximal subsequence of local steps in a dataflow path. + */ + pragma[nomagic] + predicate localFlowBigStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + DataFlowType t, LocalCallContext callContext + ) { + localFlowStepPlus(node1, state1, node2, preservesValue, t, callContext) and + localFlowExit(node2, state1) and + state1 = state2 or - exists(NodeEx mid | - localFlowStepPlus(node1, state, mid, _, _, pragma[only_bind_into](config), cc) and - additionalLocalFlowStepNodeCand2(mid, state, node2, state, config) and - not mid instanceof FlowCheckNode and - preservesValue = false and - t = node2.getDataFlowType() + additionalLocalFlowStepNodeCand2(node1, state1, node2, state2) and + state1 != state2 and + preservesValue = false and + t = node2.getDataFlowType() and + callContext.relevantFor(node1.getEnclosingCallable()) and + not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | + isUnreachableInCallCached(node1.asNode(), call) or + isUnreachableInCallCached(node2.asNode(), call) ) + } + } + + private import LocalFlowBigStep + + private module Stage3Param implements MkStage::StageParam { + private module PrevStage = Stage2; + + class Ap = ApproxAccessPathFront; + + class ApNil = ApproxAccessPathFrontNil; + + PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + ApNil getApNil(NodeEx node) { + PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) + } + + bindingset[tc, tail] + Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } + + class ApHeadContent = ContentApprox; + + pragma[noinline] + ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + predicate projectToHeadContent = getContentApprox/1; + + class ApOption = ApproxAccessPathFrontOption; + + ApOption apNone() { result = TApproxAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } + + import BooleanCallContext + + predicate localStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + ApproxAccessPathFrontNil ap, LocalCc lcc + ) { + localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), _) and + exists(lcc) + } + + predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + predicate flowIntoCall = flowIntoCallNodeCand2/4; + + pragma[nomagic] + private predicate expectsContentCand(NodeEx node, Ap ap) { + exists(Content c | + PrevStage::revFlow(node) and + PrevStage::readStepCand(_, c, _) and + expectsContentEx(node, c) and + c = ap.getAHead().getContent() + ) + } + + pragma[nomagic] + private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } + + bindingset[node, state, ap] + predicate filter(NodeEx node, FlowState state, Ap ap) { + exists(state) and + (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and + ( + notExpectsContent(node) + or + expectsContentCand(node, ap) + ) + } + + bindingset[ap, contentType] + predicate typecheckStore(Ap ap, DataFlowType contentType) { + // We need to typecheck stores here, since reverse flow through a getter + // might have a different type here compared to inside the getter. + compatibleTypes(ap.getType(), contentType) + } + } + + private module Stage3 implements StageSig { + import MkStage::Stage + } + + private module Stage4Param implements MkStage::StageParam { + private module PrevStage = Stage3; + + class Ap = AccessPathFront; + + class ApNil = AccessPathFrontNil; + + PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } + + ApNil getApNil(NodeEx node) { + PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) + } + + bindingset[tc, tail] + Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + class ApHeadContent = Content; + + pragma[noinline] + ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + ApHeadContent projectToHeadContent(Content c) { result = c } + + class ApOption = AccessPathFrontOption; + + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + + import BooleanCallContext + + pragma[nomagic] + predicate localStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + ApNil ap, LocalCc lcc + ) { + localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), _) and + PrevStage::revFlow(node1, pragma[only_bind_into](state1), _) and + PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _) and + exists(lcc) + } + + pragma[nomagic] + predicate flowOutOfCall( + DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow + ) { + exists(FlowState state | + flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow) and + PrevStage::revFlow(node2, pragma[only_bind_into](state), _) and + PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _) + ) + } + + pragma[nomagic] + predicate flowIntoCall( + DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow + ) { + exists(FlowState state | + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow) and + PrevStage::revFlow(node2, pragma[only_bind_into](state), _) and + PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _) + ) + } + + pragma[nomagic] + private predicate clearSet(NodeEx node, ContentSet c) { + PrevStage::revFlow(node) and + clearsContentCached(node.asNode(), c) + } + + pragma[nomagic] + private predicate clearContent(NodeEx node, Content c) { + exists(ContentSet cs | + PrevStage::readStepCand(_, pragma[only_bind_into](c), _) and + c = cs.getAReadContent() and + clearSet(node, cs) + ) + } + + pragma[nomagic] + private predicate clear(NodeEx node, Ap ap) { clearContent(node, ap.getHead().getContent()) } + + pragma[nomagic] + private predicate expectsContentCand(NodeEx node, Ap ap) { + exists(Content c | + PrevStage::revFlow(node) and + PrevStage::readStepCand(_, c, _) and + expectsContentEx(node, c) and + c = ap.getHead().getContent() + ) + } + + pragma[nomagic] + private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } + + bindingset[node, state, ap] + predicate filter(NodeEx node, FlowState state, Ap ap) { + exists(state) and + not clear(node, ap) and + (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and + ( + notExpectsContent(node) + or + expectsContentCand(node, ap) + ) + } + + bindingset[ap, contentType] + predicate typecheckStore(Ap ap, DataFlowType contentType) { + // We need to typecheck stores here, since reverse flow through a getter + // might have a different type here compared to inside the getter. + compatibleTypes(ap.getType(), contentType) + } + } + + private module Stage4 implements StageSig { + import MkStage::Stage + } + + /** + * Holds if `argApf` is recorded as the summary context for flow reaching `node` + * and remains relevant for the following pruning stage. + */ + private predicate flowCandSummaryCtx(NodeEx node, FlowState state, AccessPathFront argApf) { + exists(AccessPathFront apf | + Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf) and + Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf) ) } /** - * Holds if `node1` can step to `node2` in one or more local steps and this - * path can occur as a maximal subsequence of local steps in a dataflow path. + * Holds if a length 2 access path approximation with the head `tc` is expected + * to be expensive. */ - pragma[nomagic] - predicate localFlowBigStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - DataFlowType t, Configuration config, LocalCallContext callContext - ) { - localFlowStepPlus(node1, state1, node2, preservesValue, t, config, callContext) and - localFlowExit(node2, state1, config) and - state1 = state2 - or - additionalLocalFlowStepNodeCand2(node1, state1, node2, state2, config) and - state1 != state2 and - preservesValue = false and - t = node2.getDataFlowType() and - callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) - } -} - -private import LocalFlowBigStep - -private module Stage3Param implements MkStage::StageParam { - private module PrevStage = Stage2; - - class Ap = ApproxAccessPathFront; - - class ApNil = ApproxAccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } - - class ApHeadContent = ContentApprox; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - predicate projectToHeadContent = getContentApprox/1; - - class ApOption = ApproxAccessPathFrontOption; - - ApOption apNone() { result = TApproxAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } - - import BooleanCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApproxAccessPathFrontNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand2/6; - - predicate flowIntoCall = flowIntoCallNodeCand2/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getAHead().getContent() + private predicate expensiveLen2unfolding(TypedContent tc) { + exists(int tails, int nodes, int apLimit, int tupleLimit | + tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf)) and + nodes = + strictcount(NodeEx n, FlowState state | + Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc)) + or + flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc)) + ) and + accessPathApproxCostLimits(apLimit, tupleLimit) and + apLimit < tails and + tupleLimit < (tails - 1) * nodes and + not tc.forceHighPrecision() ) } - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } + private newtype TAccessPathApprox = + TNil(DataFlowType t) or + TConsNil(TypedContent tc, DataFlowType t) { + Stage4::consCand(tc, TFrontNil(t)) and + not expensiveLen2unfolding(tc) + } or + TConsCons(TypedContent tc1, TypedContent tc2, int len) { + Stage4::consCand(tc1, TFrontHead(tc2)) and + len in [2 .. accessPathLimit()] and + not expensiveLen2unfolding(tc1) + } or + TCons1(TypedContent tc, int len) { + len in [1 .. accessPathLimit()] and + expensiveLen2unfolding(tc) + } - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) + /** + * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only + * the first two elements of the list and its length are tracked. If data flows + * from a source to a given node with a given `AccessPathApprox`, this indicates + * the sequence of dereference operations needed to get from the value in the node + * to the tracked object. The final type indicates the type of the tracked object. + */ + abstract private class AccessPathApprox extends TAccessPathApprox { + abstract string toString(); + + abstract TypedContent getHead(); + + abstract int len(); + + abstract DataFlowType getType(); + + abstract AccessPathFront getFront(); + + /** Gets the access path obtained by popping `head` from this path, if any. */ + abstract AccessPathApprox pop(TypedContent head); } - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} + private class AccessPathApproxNil extends AccessPathApprox, TNil { + private DataFlowType t; -private module Stage3 implements StageSig { - import MkStage::Stage -} + AccessPathApproxNil() { this = TNil(t) } -private module Stage4Param implements MkStage::StageParam { - private module PrevStage = Stage3; + override string toString() { result = concat(": " + ppReprType(t)) } - class Ap = AccessPathFront; + override TypedContent getHead() { none() } - class ApNil = AccessPathFrontNil; + override int len() { result = 0 } - PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } + override DataFlowType getType() { result = t } - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) + override AccessPathFront getFront() { result = TFrontNil(t) } + + override AccessPathApprox pop(TypedContent head) { none() } } - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + abstract private class AccessPathApproxCons extends AccessPathApprox { } - class ApHeadContent = Content; + private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { + private TypedContent tc; + private DataFlowType t; - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } + AccessPathApproxConsNil() { this = TConsNil(tc, t) } - ApHeadContent projectToHeadContent(Content c) { result = c } + override string toString() { + // The `concat` becomes "" if `ppReprType` has no result. + result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) + } - class ApOption = AccessPathFrontOption; + override TypedContent getHead() { result = tc } - ApOption apNone() { result = TAccessPathFrontNone() } + override int len() { result = 1 } - ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + override DataFlowType getType() { result = tc.getContainerType() } - import BooleanCallContext + override AccessPathFront getFront() { result = TFrontHead(tc) } - pragma[nomagic] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) and - exists(lcc) + override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } } - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } + private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { + private TypedContent tc1; + private TypedContent tc2; + private int len; - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } + AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - pragma[nomagic] - private predicate clearSet(NodeEx node, ContentSet c, Configuration config) { - PrevStage::revFlow(node, config) and - clearsContentCached(node.asNode(), c) - } + override string toString() { + if len = 2 + then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" + else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" + } - pragma[nomagic] - private predicate clearContent(NodeEx node, Content c, Configuration config) { - exists(ContentSet cs | - PrevStage::readStepCand(_, pragma[only_bind_into](c), _, pragma[only_bind_into](config)) and - c = cs.getAReadContent() and - clearSet(node, cs, pragma[only_bind_into](config)) - ) - } + override TypedContent getHead() { result = tc1 } - pragma[nomagic] - private predicate clear(NodeEx node, Ap ap, Configuration config) { - clearContent(node, ap.getHead().getContent(), config) - } + override int len() { result = len } - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getHead().getContent() - ) - } + override DataFlowType getType() { result = tc1.getContainerType() } - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } + override AccessPathFront getFront() { result = TFrontHead(tc1) } - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - not clear(node, ap, config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage4 implements StageSig { - import MkStage::Stage -} - -/** - * Holds if `argApf` is recorded as the summary context for flow reaching `node` - * and remains relevant for the following pruning stage. - */ -private predicate flowCandSummaryCtx( - NodeEx node, FlowState state, AccessPathFront argApf, Configuration config -) { - exists(AccessPathFront apf | - Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf, config) and - Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf, - config) - ) -} - -/** - * Holds if a length 2 access path approximation with the head `tc` is expected - * to be expensive. - */ -private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { - exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf, config)) and - nodes = - strictcount(NodeEx n, FlowState state | - Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - or - flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - ) and - accessPathApproxCostLimits(apLimit, tupleLimit) and - apLimit < tails and - tupleLimit < (tails - 1) * nodes and - not tc.forceHighPrecision() - ) -} - -private newtype TAccessPathApprox = - TNil(DataFlowType t) or - TConsNil(TypedContent tc, DataFlowType t) { - Stage4::consCand(tc, TFrontNil(t), _) and - not expensiveLen2unfolding(tc, _) - } or - TConsCons(TypedContent tc1, TypedContent tc2, int len) { - Stage4::consCand(tc1, TFrontHead(tc2), _) and - len in [2 .. accessPathLimit()] and - not expensiveLen2unfolding(tc1, _) - } or - TCons1(TypedContent tc, int len) { - len in [1 .. accessPathLimit()] and - expensiveLen2unfolding(tc, _) - } - -/** - * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only - * the first two elements of the list and its length are tracked. If data flows - * from a source to a given node with a given `AccessPathApprox`, this indicates - * the sequence of dereference operations needed to get from the value in the node - * to the tracked object. The final type indicates the type of the tracked object. - */ -abstract private class AccessPathApprox extends TAccessPathApprox { - abstract string toString(); - - abstract TypedContent getHead(); - - abstract int len(); - - abstract DataFlowType getType(); - - abstract AccessPathFront getFront(); - - /** Gets the access path obtained by popping `head` from this path, if any. */ - abstract AccessPathApprox pop(TypedContent head); -} - -private class AccessPathApproxNil extends AccessPathApprox, TNil { - private DataFlowType t; - - AccessPathApproxNil() { this = TNil(t) } - - override string toString() { result = concat(": " + ppReprType(t)) } - - override TypedContent getHead() { none() } - - override int len() { result = 0 } - - override DataFlowType getType() { result = t } - - override AccessPathFront getFront() { result = TFrontNil(t) } - - override AccessPathApprox pop(TypedContent head) { none() } -} - -abstract private class AccessPathApproxCons extends AccessPathApprox { } - -private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { - private TypedContent tc; - private DataFlowType t; - - AccessPathApproxConsNil() { this = TConsNil(tc, t) } - - override string toString() { - // The `concat` becomes "" if `ppReprType` has no result. - result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) - } - - override TypedContent getHead() { result = tc } - - override int len() { result = 1 } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } -} - -private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { - private TypedContent tc1; - private TypedContent tc2; - private int len; - - AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - - override string toString() { - if len = 2 - then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" - else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc1 } - - override int len() { result = len } - - override DataFlowType getType() { result = tc1.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc1) } - - override AccessPathApprox pop(TypedContent head) { - head = tc1 and - ( - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - } -} - -private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { - private TypedContent tc; - private int len; - - AccessPathApproxCons1() { this = TCons1(tc, len) } - - override string toString() { - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc } - - override int len() { result = len } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { - head = tc and - ( - exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2), _) | + override AccessPathApprox pop(TypedContent head) { + head = tc1 and + ( result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -2763,1561 +2471,585 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or result = TCons1(tc2, len - 1) ) - or - exists(DataFlowType t | - len = 1 and - Stage4::consCand(tc, TFrontNil(t), _) and - result = TNil(t) + } + } + + private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { + private TypedContent tc; + private int len; + + AccessPathApproxCons1() { this = TCons1(tc, len) } + + override string toString() { + if len = 1 + then result = "[" + tc.toString() + "]" + else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" + } + + override TypedContent getHead() { result = tc } + + override int len() { result = len } + + override DataFlowType getType() { result = tc.getContainerType() } + + override AccessPathFront getFront() { result = TFrontHead(tc) } + + override AccessPathApprox pop(TypedContent head) { + head = tc and + ( + exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2)) | + result = TConsCons(tc2, _, len - 1) + or + len = 2 and + result = TConsNil(tc2, _) + or + result = TCons1(tc2, len - 1) + ) + or + exists(DataFlowType t | + len = 1 and + Stage4::consCand(tc, TFrontNil(t)) and + result = TNil(t) + ) ) - ) + } } -} -/** Gets the access path obtained by popping `tc` from `ap`, if any. */ -private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } + /** Gets the access path obtained by popping `tc` from `ap`, if any. */ + private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } -/** Gets the access path obtained by pushing `tc` onto `ap`. */ -private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } + /** Gets the access path obtained by pushing `tc` onto `ap`. */ + private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } -private newtype TAccessPathApproxOption = - TAccessPathApproxNone() or - TAccessPathApproxSome(AccessPathApprox apa) + private newtype TAccessPathApproxOption = + TAccessPathApproxNone() or + TAccessPathApproxSome(AccessPathApprox apa) -private class AccessPathApproxOption extends TAccessPathApproxOption { - string toString() { - this = TAccessPathApproxNone() and result = "" - or - this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) + private class AccessPathApproxOption extends TAccessPathApproxOption { + string toString() { + this = TAccessPathApproxNone() and result = "" + or + this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) + } } -} -private module Stage5Param implements MkStage::StageParam { - private module PrevStage = Stage4; + private module Stage5Param implements MkStage::StageParam { + private module PrevStage = Stage4; - class Ap = AccessPathApprox; + class Ap = AccessPathApprox; - class ApNil = AccessPathApproxNil; + class ApNil = AccessPathApproxNil; + + pragma[nomagic] + PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } + + ApNil getApNil(NodeEx node) { + PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) + } + + bindingset[tc, tail] + Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + class ApHeadContent = Content; + + pragma[noinline] + ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + ApHeadContent projectToHeadContent(Content c) { result = c } + + class ApOption = AccessPathApproxOption; + + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + + import Level1CallContext + import LocalCallContext + + predicate localStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + ApNil ap, LocalCc lcc + ) { + localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), lcc) and + PrevStage::revFlow(node1, pragma[only_bind_into](state1), _) and + PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _) + } + + pragma[nomagic] + predicate flowOutOfCall( + DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow + ) { + exists(FlowState state | + flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow) and + PrevStage::revFlow(node2, pragma[only_bind_into](state), _) and + PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _) + ) + } + + pragma[nomagic] + predicate flowIntoCall( + DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow + ) { + exists(FlowState state | + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow) and + PrevStage::revFlow(node2, pragma[only_bind_into](state), _) and + PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _) + ) + } + + bindingset[node, state, ap] + predicate filter(NodeEx node, FlowState state, Ap ap) { any() } + + // Type checking is not necessary here as it has already been done in stage 3. + bindingset[ap, contentType] + predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + } + + private module Stage5 = MkStage::Stage; pragma[nomagic] - PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathApproxOption; - - ApOption apNone() { result = TAccessPathApproxNone() } - - ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } - - import Level1CallContext - import LocalCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc + private predicate nodeMayUseSummary0( + NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, lcc) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) + exists(AccessPathApprox apa0 | + Stage5::parameterMayFlowThrough(p, _) and + Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0) and + Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), + TAccessPathApproxSome(apa), apa0) ) } pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) + private predicate nodeMayUseSummary(NodeEx n, FlowState state, AccessPathApprox apa) { + exists(ParamNodeEx p | + Stage5::parameterMayFlowThrough(p, apa) and + nodeMayUseSummary0(n, p, state, apa) ) } - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { any() } + private newtype TSummaryCtx = + TSummaryCtxNone() or + TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { + Stage5::parameterMayFlowThrough(p, ap.getApprox()) and + Stage5::revFlow(p, state, _) + } - // Type checking is not necessary here as it has already been done in stage 3. - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} + /** + * A context for generating flow summaries. This represents flow entry through + * a specific parameter with an access path of a specific shape. + * + * Summaries are only created for parameters that may flow through. + */ + abstract private class SummaryCtx extends TSummaryCtx { + abstract string toString(); + } -private module Stage5 = MkStage::Stage; + /** A summary context from which no flow summary can be generated. */ + private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { + override string toString() { result = "" } + } -bindingset[conf, result] -private Configuration unbindConf(Configuration conf) { - exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c)) -} + /** A summary context from which a flow summary can be generated. */ + private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { + private ParamNodeEx p; + private FlowState s; + private AccessPath ap; -pragma[nomagic] -private predicate nodeMayUseSummary0( - NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(AccessPathApprox apa0 | - Stage5::parameterMayFlowThrough(p, _, _) and - Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0, config) and - Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), - TAccessPathApproxSome(apa), apa0, config) - ) -} + SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } -pragma[nomagic] -private predicate nodeMayUseSummary( - NodeEx n, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::parameterMayFlowThrough(p, apa, config) and - nodeMayUseSummary0(n, p, state, apa, config) - ) -} + ParameterPosition getParameterPos() { p.isParameterOf(_, result) } -private newtype TSummaryCtx = - TSummaryCtxNone() or - TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { - exists(Configuration config | - Stage5::parameterMayFlowThrough(p, ap.getApprox(), config) and - Stage5::revFlow(p, state, _, config) + ParamNodeEx getParamNode() { result = p } + + override string toString() { result = p + ": " + ap } + + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * Gets the number of length 2 access path approximations that correspond to `apa`. + */ + private int count1to2unfold(AccessPathApproxCons1 apa) { + exists(TypedContent tc, int len | + tc = apa.getHead() and + len = apa.len() and + result = + strictcount(AccessPathFront apf | + Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1)) + ) ) } -/** - * A context for generating flow summaries. This represents flow entry through - * a specific parameter with an access path of a specific shape. - * - * Summaries are only created for parameters that may flow through. - */ -abstract private class SummaryCtx extends TSummaryCtx { - abstract string toString(); -} - -/** A summary context from which no flow summary can be generated. */ -private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { - override string toString() { result = "" } -} - -/** A summary context from which a flow summary can be generated. */ -private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParamNodeEx p; - private FlowState s; - private AccessPath ap; - - SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } - - ParameterPosition getParameterPos() { p.isParameterOf(_, result) } - - ParamNodeEx getParamNode() { result = p } - - override string toString() { result = p + ": " + ap } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** - * Gets the number of length 2 access path approximations that correspond to `apa`. - */ -private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { - exists(TypedContent tc, int len | - tc = apa.getHead() and - len = apa.len() and + private int countNodesUsingAccessPath(AccessPathApprox apa) { result = - strictcount(AccessPathFront apf | - Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), - config) + strictcount(NodeEx n, FlowState state | + Stage5::revFlow(n, state, apa) or nodeMayUseSummary(n, state, apa) ) - ) -} + } -private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = - strictcount(NodeEx n, FlowState state | - Stage5::revFlow(n, state, apa, config) or nodeMayUseSummary(n, state, apa, config) - ) -} - -/** - * Holds if a length 2 access path approximation matching `apa` is expected - * to be expensive. - */ -private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = count1to2unfold(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - apLimit < aps and - tupleLimit < (aps - 1) * nodes - ) -} - -private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { - exists(TypedContent head | - apa.pop(head) = result and - Stage5::consCand(head, result, config) - ) -} - -/** - * Holds with `unfold = false` if a precise head-tail representation of `apa` is - * expected to be expensive. Holds with `unfold = true` otherwise. - */ -private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { - if apa.getHead().forceHighPrecision() - then unfold = true - else + /** + * Holds if a length 2 access path approximation matching `apa` is expected + * to be expensive. + */ + private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa) { exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = countPotentialAps(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and + aps = count1to2unfold(apa) and + nodes = countNodesUsingAccessPath(apa) and accessPathCostLimits(apLimit, tupleLimit) and - if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true - ) -} - -/** - * Gets the number of `AccessPath`s that correspond to `apa`. - */ -pragma[assume_small_delta] -private int countAps(AccessPathApprox apa, Configuration config) { - evalUnfold(apa, false, config) and - result = 1 and - (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) - or - evalUnfold(apa, false, config) and - result = count1to2unfold(apa, config) and - not expensiveLen1to2unfolding(apa, config) - or - evalUnfold(apa, true, config) and - result = countPotentialAps(apa, config) -} - -/** - * Gets the number of `AccessPath`s that would correspond to `apa` assuming - * that it is expanded to a precise head-tail representation. - */ -language[monotonicAggregates] -pragma[assume_small_delta] -private int countPotentialAps(AccessPathApprox apa, Configuration config) { - apa instanceof AccessPathApproxNil and result = 1 - or - result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) -} - -private newtype TAccessPath = - TAccessPathNil(DataFlowType t) or - TAccessPathCons(TypedContent head, AccessPath tail) { - exists(AccessPathApproxCons apa | - not evalUnfold(apa, false, _) and - head = apa.getHead() and - tail.getApprox() = getATail(apa, _) - ) - } or - TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - not expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head1 = apa.getHead() and - head2 = getATail(apa, _).getHead() - ) - } or - TAccessPathCons1(TypedContent head, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head = apa.getHead() + apLimit < aps and + tupleLimit < (aps - 1) * nodes ) } -private newtype TPathNode = - pragma[assume_small_delta] - TPathNodeMid( - NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config - ) { - // A PathNode is introduced by a source ... - Stage5::revFlow(node, state, config) and - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - or - // ... or a step from an existing PathNode to another node. - exists(PathNodeMid mid | - pathStep(mid, node, state, cc, sc, ap) and - pragma[only_bind_into](config) = mid.getConfiguration() and - Stage5::revFlow(node, state, ap.getApprox(), pragma[only_bind_into](config)) - ) - } or - TPathNodeSink(NodeEx node, FlowState state, Configuration config) { - exists(PathNodeMid sink | - sink.isAtSink() and - node = sink.getNodeEx() and - state = sink.getState() and - config = sink.getConfiguration() - ) - } or - TPathNodeSourceGroup(string sourceGroup, Configuration config) { - exists(PathNodeImpl source | - sourceGroup = source.getSourceGroup() and - config = source.getConfiguration() - ) - } or - TPathNodeSinkGroup(string sinkGroup, Configuration config) { - exists(PathNodeSink sink | - sinkGroup = sink.getSinkGroup() and - config = sink.getConfiguration() + private AccessPathApprox getATail(AccessPathApprox apa) { + exists(TypedContent head | + apa.pop(head) = result and + Stage5::consCand(head, result) ) } -/** - * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a - * source to a given node with a given `AccessPath`, this indicates the sequence - * of dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ -private class AccessPath extends TAccessPath { - /** Gets the head of this access path, if any. */ - abstract TypedContent getHead(); - - /** Gets the tail of this access path, if any. */ - abstract AccessPath getTail(); - - /** Gets the front of this access path. */ - abstract AccessPathFront getFront(); - - /** Gets the approximation of this access path. */ - abstract AccessPathApprox getApprox(); - - /** Gets the length of this access path. */ - abstract int length(); - - /** Gets a textual representation of this access path. */ - abstract string toString(); - - /** Gets the access path obtained by popping `tc` from this access path, if any. */ - final AccessPath pop(TypedContent tc) { - result = this.getTail() and - tc = this.getHead() - } - - /** Gets the access path obtained by pushing `tc` onto this access path. */ - final AccessPath push(TypedContent tc) { this = result.pop(tc) } -} - -private class AccessPathNil extends AccessPath, TAccessPathNil { - private DataFlowType t; - - AccessPathNil() { this = TAccessPathNil(t) } - - DataFlowType getType() { result = t } - - override TypedContent getHead() { none() } - - override AccessPath getTail() { none() } - - override AccessPathFrontNil getFront() { result = TFrontNil(t) } - - override AccessPathApproxNil getApprox() { result = TNil(t) } - - override int length() { result = 0 } - - override string toString() { result = concat(": " + ppReprType(t)) } -} - -private class AccessPathCons extends AccessPath, TAccessPathCons { - private TypedContent head; - private AccessPath tail; - - AccessPathCons() { this = TAccessPathCons(head, tail) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { result = tail } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - pragma[assume_small_delta] - override AccessPathApproxCons getApprox() { - result = TConsNil(head, tail.(AccessPathNil).getType()) - or - result = TConsCons(head, tail.getHead(), this.length()) - or - result = TCons1(head, this.length()) - } - - pragma[assume_small_delta] - override int length() { result = 1 + tail.length() } - - private string toStringImpl(boolean needsSuffix) { - exists(DataFlowType t | - tail = TAccessPathNil(t) and - needsSuffix = false and - result = head.toString() + "]" + concat(" : " + ppReprType(t)) - ) - or - result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) - or - exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | - result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true - or - result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false - ) - or - exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | - result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true - or - result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false - ) - } - - override string toString() { - result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" - or - result = "[" + this.toStringImpl(false) - } -} - -private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { - private TypedContent head1; - private TypedContent head2; - private int len; - - AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } - - override TypedContent getHead() { result = head1 } - - override AccessPath getTail() { - Stage5::consCand(head1, result.getApprox(), _) and - result.getHead() = head2 and - result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head1) } - - override AccessPathApproxCons getApprox() { - result = TConsCons(head1, head2, len) or - result = TCons1(head1, len) - } - - override int length() { result = len } - - override string toString() { - if len = 2 - then result = "[" + head1.toString() + ", " + head2.toString() + "]" + /** + * Holds with `unfold = false` if a precise head-tail representation of `apa` is + * expected to be expensive. Holds with `unfold = true` otherwise. + */ + private predicate evalUnfold(AccessPathApprox apa, boolean unfold) { + if apa.getHead().forceHighPrecision() + then unfold = true else - result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" - } -} - -private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { - private TypedContent head; - private int len; - - AccessPathCons1() { this = TAccessPathCons1(head, len) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { - Stage5::consCand(head, result.getApprox(), _) and result.length() = len - 1 + exists(int aps, int nodes, int apLimit, int tupleLimit | + aps = countPotentialAps(apa) and + nodes = countNodesUsingAccessPath(apa) and + accessPathCostLimits(apLimit, tupleLimit) and + if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true + ) } - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - - override int length() { result = len } - - override string toString() { - if len = 1 - then result = "[" + head.toString() + "]" - else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" - } -} - -abstract private class PathNodeImpl extends TPathNode { - /** Gets the `FlowState` of this node. */ - abstract FlowState getState(); - - /** Gets the associated configuration. */ - abstract Configuration getConfiguration(); - - /** Holds if this node is a source. */ - abstract predicate isSource(); - - abstract PathNodeImpl getASuccessorImpl(); - - private PathNodeImpl getASuccessorIfHidden() { - this.isHidden() and - result = this.getASuccessorImpl() - } - - pragma[nomagic] - private PathNodeImpl getANonHiddenSuccessor0() { - result = this.getASuccessorIfHidden*() and - not result.isHidden() - } - - final PathNodeImpl getANonHiddenSuccessor() { - result = this.getASuccessorImpl().getANonHiddenSuccessor0() and - not this.isHidden() - } - - abstract NodeEx getNodeEx(); - - predicate isHidden() { - not this.getConfiguration().includeHiddenNodes() and - ( - hiddenNode(this.getNodeEx().asNode()) and - not this.isSource() and - not this instanceof PathNodeSink - or - this.getNodeEx() instanceof TNodeImplicitRead - ) - } - - string getSourceGroup() { - this.isSource() and - this.getConfiguration().sourceGrouping(this.getNodeEx().asNode(), result) - } - - predicate isFlowSource() { - this.isSource() and not exists(this.getSourceGroup()) + /** + * Gets the number of `AccessPath`s that correspond to `apa`. + */ + pragma[assume_small_delta] + private int countAps(AccessPathApprox apa) { + evalUnfold(apa, false) and + result = 1 and + (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa)) or - this instanceof PathNodeSourceGroup - } - - predicate isFlowSink() { - this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or - this instanceof PathNodeSinkGroup - } - - private string ppAp() { - this instanceof PathNodeSink and result = "" + evalUnfold(apa, false) and + result = count1to2unfold(apa) and + not expensiveLen1to2unfolding(apa) or - exists(string s | s = this.(PathNodeMid).getAp().toString() | - if s = "" then result = "" else result = " " + s - ) + evalUnfold(apa, true) and + result = countPotentialAps(apa) } - private string ppCtx() { - this instanceof PathNodeSink and result = "" + /** + * Gets the number of `AccessPath`s that would correspond to `apa` assuming + * that it is expanded to a precise head-tail representation. + */ + language[monotonicAggregates] + pragma[assume_small_delta] + private int countPotentialAps(AccessPathApprox apa) { + apa instanceof AccessPathApproxNil and result = 1 or - result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + result = strictsum(AccessPathApprox tail | tail = getATail(apa) | countAps(tail)) } - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** Holds if `n` can reach a sink. */ -private predicate directReach(PathNodeImpl n) { - n instanceof PathNodeSink or - n instanceof PathNodeSinkGroup or - directReach(n.getANonHiddenSuccessor()) -} - -/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ -private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } - -/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { - n1.getANonHiddenSuccessor() = n2 and directReach(n2) -} - -private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) - -/** - * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. - * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. - */ -class PathNode instanceof PathNodeImpl { - PathNode() { reach(this) } - - /** Gets a textual representation of this element. */ - final string toString() { result = super.toString() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - final string toStringWithContext() { result = super.toStringWithContext() } - - /** - * 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/). - */ - final predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - /** Gets the underlying `Node`. */ - final Node getNode() { super.getNodeEx().projectToNode() = result } - - /** Gets the `FlowState` of this node. */ - final FlowState getState() { result = super.getState() } - - /** Gets the associated configuration. */ - final Configuration getConfiguration() { result = super.getConfiguration() } - - /** Gets a successor of this node, if any. */ - final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } - - /** Holds if this node is a source. */ - final predicate isSource() { super.isSource() } - - /** Holds if this node is a grouping of source nodes. */ - final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group, _) } - - /** Holds if this node is a grouping of sink nodes. */ - final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group, _) } -} - -/** - * Provides the query predicates needed to include a graph in a path-problem query. - */ -module PathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } - - /** Holds if `n` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode n, string key, string val) { - key = "semmle.label" and val = n.toString() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { - Subpaths::subpaths(arg, par, ret, out) - } -} - -/** - * An intermediate flow graph node. This is a triple consisting of a `Node`, - * a `CallContext`, and a `Configuration`. - */ -private class PathNodeMid extends PathNodeImpl, TPathNodeMid { - NodeEx node; - FlowState state; - CallContext cc; - SummaryCtx sc; - AccessPath ap; - Configuration config; - - PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - SummaryCtx getSummaryCtx() { result = sc } - - AccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - private PathNodeMid getSuccMid() { - pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx(), result.getAp()) and - result.getConfiguration() = unbindConf(this.getConfiguration()) - } - - override PathNodeImpl getASuccessorImpl() { - // an intermediate step to another intermediate node - result = this.getSuccMid() - or - // a final step to a sink - result = this.getSuccMid().projectToSink() - } - - override predicate isSource() { - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - } - - predicate isAtSink() { - sinkNode(node, state, config) and - ap instanceof AccessPathNil and - if hasSinkCallCtx(config) - then - // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` - // is exactly what we need to check. This also implies - // `sc instanceof SummaryCtxNone`. - // For `FeatureEqualSourceSinkCallContext` the initial call context was - // set to `CallContextSomeCall` and jumps are disallowed, so - // `cc instanceof CallContextNoCall` never holds. On the other hand, - // in this case there's never any need to enter a call except to identify - // a summary, so the condition in `pathIntoCallable` enforces this, which - // means that `sc instanceof SummaryCtxNone` holds if and only if we are - // in the call context of the source. - sc instanceof SummaryCtxNone or - cc instanceof CallContextNoCall - else any() - } - - PathNodeSink projectToSink() { - this.isAtSink() and - result.getNodeEx() = node and - result.getState() = state and - result.getConfiguration() = unbindConf(config) - } -} - -/** - * A flow graph node corresponding to a sink. This is disjoint from the - * intermediate nodes in order to uniquely correspond to a given sink by - * excluding the `CallContext`. - */ -private class PathNodeSink extends PathNodeImpl, TPathNodeSink { - NodeEx node; - FlowState state; - Configuration config; - - PathNodeSink() { this = TPathNodeSink(node, state, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result = TPathNodeSinkGroup(this.getSinkGroup(), config) - } - - override predicate isSource() { sourceNode(node, state, config) } - - string getSinkGroup() { config.sinkGrouping(node.asNode(), result) } -} - -private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { - string sourceGroup; - Configuration config; - - PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result.getSourceGroup() = sourceGroup and - result.getConfiguration() = config - } - - override predicate isSource() { none() } - - override string toString() { result = sourceGroup } - - override 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 - } -} - -private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { - string sinkGroup; - Configuration config; - - PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { none() } - - override predicate isSource() { none() } - - override string toString() { result = sinkGroup } - - override 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 - } -} - -private predicate pathNode( - PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, - Configuration conf, LocalCallContext localCC -) { - midnode = mid.getNodeEx() and - state = mid.getState() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - midnode.getEnclosingCallable()) and - ap = mid.getAp() -} - -/** - * Holds if data may flow from `mid` to `node`. The last step in or out of - * a callable is recorded by `cc`. - */ -pragma[assume_small_delta] -pragma[nomagic] -private predicate pathStep( - PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap -) { - exists(NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC | - pathNode(mid, midnode, state0, cc, sc, ap, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, true, _, conf, localCC) - ) - or - exists( - AccessPath ap0, NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC - | - pathNode(mid, midnode, state0, cc, sc, ap0, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), conf, - localCC) and - ap0 instanceof AccessPathNil - ) - or - jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - ap = mid.getAp() - or - additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, mid.getConfiguration()) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - pathIntoCallable(mid, node, state, _, cc, sc, _, _) and ap = mid.getAp() - or - pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone - or - pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() -} - -pragma[nomagic] -private predicate pathReadStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - tc = ap0.getHead() and - Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -pragma[nomagic] -private predicate pathStoreStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -private predicate pathOutOfCallable0( - PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa, - Configuration config -) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - apa = mid.getAp().getApprox() and - config = mid.getConfiguration() -} - -pragma[nomagic] -private predicate pathOutOfCallable1( - PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPathApprox apa, Configuration config -) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - pathOutOfCallable0(mid, pos, state, innercc, apa, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) -} - -pragma[noinline] -private NodeEx getAnOutNodeFlow( - ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config -) { - result.asNode() = kind.getAnOutNode(call) and - Stage5::revFlow(result, _, apa, config) -} - -/** - * Holds if data may flow from `mid` to `out`. The last step of this path - * is a return from a callable and is recorded by `cc`, if needed. - */ -pragma[noinline] -private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { - exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | - pathOutOfCallable1(mid, call, kind, state, cc, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. - */ -pragma[noinline] -private predicate pathIntoArg( - PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(ArgNodeEx arg, ArgumentPosition apos | - pathNode(mid, arg, state, cc, _, ap, config, _) and - arg.asNode().(ArgNode).argumentOf(call, apos) and - apa = ap.getApprox() and - parameterMatch(ppos, apos) - ) -} - -pragma[nomagic] -private predicate parameterCand( - DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::revFlow(p, _, apa, config) and - p.isParameterOf(callable, pos) - ) -} - -pragma[nomagic] -private predicate pathIntoCallable0( - PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, AccessPath ap, Configuration config -) { - exists(AccessPathApprox apa | - pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - callable = resolveCall(call, outercc) and - parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa), - pragma[only_bind_into](config)) - ) -} - -/** - * Holds if data may flow from `mid` to `p` through `call`. The contexts - * before and after entering the callable are `outercc` and `innercc`, - * respectively. - */ -pragma[nomagic] -private predicate pathIntoCallable( - PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, - SummaryCtx sc, DataFlowCall call, Configuration config -) { - exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | - pathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - ( - sc = TSummaryCtxSome(p, state, ap) - or - not exists(TSummaryCtxSome(p, state, ap)) and - sc = TSummaryCtxNone() and - // When the call contexts of source and sink needs to match then there's - // never any reason to enter a callable except to find a summary. See also - // the comment in `PathNodeMid::isAtSink`. - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) -} - -/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ -pragma[nomagic] -private predicate paramFlowsThrough( - ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, - AccessPathApprox apa, Configuration config -) { - exists(RetNodeEx ret | - pathNode(_, ret, state, cc, sc, ap, config, _) and - kind = ret.getKind() and - apa = ap.getApprox() and - parameterFlowThroughAllowed(sc.getParamNode(), kind) - ) -} - -pragma[nomagic] -private predicate pathThroughCallable0( - DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(CallContext innercc, SummaryCtx sc | - pathIntoCallable(mid, _, _, cc, innercc, sc, call, config) and - paramFlowsThrough(kind, state, innercc, sc, ap, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` through a callable to the node `out`. - * The context `cc` is restored to its value prior to entering the callable. - */ -pragma[noinline] -private predicate pathThroughCallable( - PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap -) { - exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config | - pathThroughCallable0(call, mid, kind, state, cc, ap, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -private module Subpaths { - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths01( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - exists(Configuration config | - pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and - pathIntoCallable(arg, par, _, _, innercc, sc, _, config) and - paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, - pragma[only_bind_into](apout), _, unbindConf(config)) and - not arg.isHidden() - ) - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `sout`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths02( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and - out.asNode() = kind.getAnOutNode(_) - } - - pragma[nomagic] - private Configuration getPathNodeConf(PathNodeImpl n) { result = n.getConfiguration() } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple. - */ - pragma[nomagic] - private predicate subpaths03( - PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, AccessPath apout - ) { - exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | - subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and - pathNode(ret, retnode, sout, innercc, sc, apout, unbindConf(getPathNodeConf(arg)), _) and - kind = retnode.getKind() - ) - } - - private PathNodeImpl localStepToHidden(PathNodeImpl n) { - n.getASuccessorImpl() = result and - result.isHidden() and - exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | - localFlowBigStep(n1, _, n2, _, _, _, _, _) or - store(n1, _, n2, _, _) or - readSet(n1, _, n2, _) - ) - } - - pragma[nomagic] - private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { - succ = pred.getANonHiddenSuccessor() and - succNode = succ.getNodeEx() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { - exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | - pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and - subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and - hasSuccessor(pragma[only_bind_into](arg), par, p) and - not ret.isHidden() and - pathNode(out0, o, sout, _, _, apout, _, _) - | - out = out0 or out = out0.projectToSink() - ) - } - - /** - * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. - */ - predicate retReach(PathNodeImpl n) { - exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) - or - exists(PathNodeImpl mid | - retReach(mid) and - n.getANonHiddenSuccessor() = mid and - not subpaths(_, mid, _, _) - ) - } -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -private predicate hasFlowPath( - PathNodeImpl flowsource, PathNodeImpl flowsink, Configuration configuration -) { - flowsource.isFlowSource() and - flowsource.getConfiguration() = configuration and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.isFlowSink() -} - -private predicate flowsTo( - PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink, - Configuration configuration -) { - flowsource.isSource() and - flowsource.getConfiguration() = configuration and - flowsource.getNodeEx().asNode() = source and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.getNodeEx().asNode() = sink -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -predicate flowsTo(Node source, Node sink, Configuration configuration) { - flowsTo(_, _, source, sink, configuration) -} - -private predicate finalStats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples -) { - fwd = true and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and - tuples = count(PathNodeImpl pn) - or - fwd = false and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and - tuples = count(PathNode pn) -} - -/** - * INTERNAL: Only for debugging. - * - * Calculates per-stage metrics for data flow. - */ -predicate stageStats( - int n, string stage, int nodes, int fields, int conscand, int states, int tuples, - Configuration config -) { - stage = "1 Fwd" and - n = 10 and - Stage1::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "1 Rev" and - n = 15 and - Stage1::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Fwd" and - n = 20 and - Stage2::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Rev" and - n = 25 and - Stage2::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Fwd" and - n = 30 and - Stage3::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Rev" and - n = 35 and - Stage3::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Fwd" and - n = 40 and - Stage4::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Rev" and - n = 45 and - Stage4::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Fwd" and - n = 50 and - Stage5::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Rev" and - n = 55 and - Stage5::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) - or - stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) -} - -private module FlowExploration { - private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - jumpStep(node1, node2, config) - or - additionalJumpStep(node1, node2, config) - or - additionalJumpStateStep(node1, _, node2, _, config) - or - // flow into callable - viableParamArgEx(_, node2, node1) - or - // flow out of a callable - viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) - | - c1 = node1.getEnclosingCallable() and - c2 = node2.getEnclosingCallable() and - c1 != c2 - ) - } - - private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSource(n) or config.isSource(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSrc(mid, config) and callableStep(mid, c, config) - ) - } - - private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSink(n) or config.isSink(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSink(mid, config) and callableStep(c, mid, config) - ) - } - - private newtype TCallableExt = - TCallable(DataFlowCallable c, Configuration config) { - interestingCallableSrc(c, config) or - interestingCallableSink(c, config) + private newtype TAccessPath = + TAccessPathNil(DataFlowType t) or + TAccessPathCons(TypedContent head, AccessPath tail) { + exists(AccessPathApproxCons apa | + not evalUnfold(apa, false) and + head = apa.getHead() and + tail.getApprox() = getATail(apa) + ) } or - TCallableSrc() or - TCallableSink() + TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { + exists(AccessPathApproxCons apa | + evalUnfold(apa, false) and + not expensiveLen1to2unfolding(apa) and + apa.len() = len and + head1 = apa.getHead() and + head2 = getATail(apa).getHead() + ) + } or + TAccessPathCons1(TypedContent head, int len) { + exists(AccessPathApproxCons apa | + evalUnfold(apa, false) and + expensiveLen1to2unfolding(apa) and + apa.len() = len and + head = apa.getHead() + ) + } - private predicate callableExtSrc(TCallableSrc src) { any() } - - private predicate callableExtSink(TCallableSink sink) { any() } - - private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { - exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | - callableStep(c1, c2, config) and - ce1 = TCallable(c1, pragma[only_bind_into](config)) and - ce2 = TCallable(c2, pragma[only_bind_into](config)) - ) - or - exists(Node n, Configuration config | - ce1 = TCallableSrc() and - (config.isSource(n) or config.isSource(n, _)) and - ce2 = TCallable(getNodeEnclosingCallable(n), config) - ) - or - exists(Node n, Configuration config | - ce2 = TCallableSink() and - (config.isSink(n) or config.isSink(n, _)) and - ce1 = TCallable(getNodeEnclosingCallable(n), config) - ) - } - - private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { - callableExtStepFwd(ce2, ce1) - } - - private int distSrcExt(TCallableExt c) = - shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) - - private int distSinkExt(TCallableExt c) = - shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) - - private int distSrc(DataFlowCallable c, Configuration config) { - result = distSrcExt(TCallable(c, config)) - 1 - } - - private int distSink(DataFlowCallable c, Configuration config) { - result = distSinkExt(TCallable(c, config)) - 1 - } - - private newtype TPartialAccessPath = - TPartialNil(DataFlowType t) or - TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } + private newtype TPathNode = + pragma[assume_small_delta] + TPathNodeMid(NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap) { + // A PathNode is introduced by a source ... + Stage5::revFlow(node, state) and + sourceNode(node, state) and + ( + if hasSourceCallCtx() + then cc instanceof CallContextSomeCall + else cc instanceof CallContextAny + ) and + sc instanceof SummaryCtxNone and + ap = TAccessPathNil(node.getDataFlowType()) + or + // ... or a step from an existing PathNode to another node. + pathStep(_, node, state, cc, sc, ap) and + Stage5::revFlow(node, state, ap.getApprox()) + } or + TPathNodeSink(NodeEx node, FlowState state) { + exists(PathNodeMid sink | + sink.isAtSink() and + node = sink.getNodeEx() and + state = sink.getState() + ) + } or + TPathNodeSourceGroup(string sourceGroup) { + exists(PathNodeImpl source | sourceGroup = source.getSourceGroup()) + } or + TPathNodeSinkGroup(string sinkGroup) { + exists(PathNodeSink sink | sinkGroup = sink.getSinkGroup()) + } /** - * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first - * element of the list and its length are tracked. If data flows from a source to - * a given node with a given `AccessPath`, this indicates the sequence of - * dereference operations needed to get from the value in the node to the + * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a + * source to a given node with a given `AccessPath`, this indicates the sequence + * of dereference operations needed to get from the value in the node to the * tracked object. The final type indicates the type of the tracked object. */ - private class PartialAccessPath extends TPartialAccessPath { + private class AccessPath extends TAccessPath { + /** Gets the head of this access path, if any. */ + abstract TypedContent getHead(); + + /** Gets the tail of this access path, if any. */ + abstract AccessPath getTail(); + + /** Gets the front of this access path. */ + abstract AccessPathFront getFront(); + + /** Gets the approximation of this access path. */ + abstract AccessPathApprox getApprox(); + + /** Gets the length of this access path. */ + abstract int length(); + + /** Gets a textual representation of this access path. */ abstract string toString(); - TypedContent getHead() { this = TPartialCons(result, _) } - - int len() { - this = TPartialNil(_) and result = 0 - or - this = TPartialCons(_, result) + /** Gets the access path obtained by popping `tc` from this access path, if any. */ + final AccessPath pop(TypedContent tc) { + result = this.getTail() and + tc = this.getHead() } - DataFlowType getType() { - this = TPartialNil(result) - or - exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) - } + /** Gets the access path obtained by pushing `tc` onto this access path. */ + final AccessPath push(TypedContent tc) { this = result.pop(tc) } } - private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { - override string toString() { - exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) - } + private class AccessPathNil extends AccessPath, TAccessPathNil { + private DataFlowType t; + + AccessPathNil() { this = TAccessPathNil(t) } + + DataFlowType getType() { result = t } + + override TypedContent getHead() { none() } + + override AccessPath getTail() { none() } + + override AccessPathFrontNil getFront() { result = TFrontNil(t) } + + override AccessPathApproxNil getApprox() { result = TNil(t) } + + override int length() { result = 0 } + + override string toString() { result = concat(": " + ppReprType(t)) } } - private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { - override string toString() { - exists(TypedContent tc, int len | this = TPartialCons(tc, len) | - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" + private class AccessPathCons extends AccessPath, TAccessPathCons { + private TypedContent head; + private AccessPath tail; + + AccessPathCons() { this = TAccessPathCons(head, tail) } + + override TypedContent getHead() { result = head } + + override AccessPath getTail() { result = tail } + + override AccessPathFrontHead getFront() { result = TFrontHead(head) } + + pragma[assume_small_delta] + override AccessPathApproxCons getApprox() { + result = TConsNil(head, tail.(AccessPathNil).getType()) + or + result = TConsCons(head, tail.getHead(), this.length()) + or + result = TCons1(head, this.length()) + } + + pragma[assume_small_delta] + override int length() { result = 1 + tail.length() } + + private string toStringImpl(boolean needsSuffix) { + exists(DataFlowType t | + tail = TAccessPathNil(t) and + needsSuffix = false and + result = head.toString() + "]" + concat(" : " + ppReprType(t)) + ) + or + result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) + or + exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | + result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true + or + result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false + ) + or + exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | + result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true + or + result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false ) } - } - private newtype TRevPartialAccessPath = - TRevPartialNil() or - TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `Content`s, but only the first - * element of the list and its length are tracked. - */ - private class RevPartialAccessPath extends TRevPartialAccessPath { - abstract string toString(); - - Content getHead() { this = TRevPartialCons(result, _) } - - int len() { - this = TRevPartialNil() and result = 0 - or - this = TRevPartialCons(_, result) - } - } - - private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { - override string toString() { result = "" } - } - - private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { override string toString() { - exists(Content c, int len | this = TRevPartialCons(c, len) | - if len = 1 - then result = "[" + c.toString() + "]" - else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" - ) + result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" + or + result = "[" + this.toStringImpl(false) } } - private predicate relevantState(FlowState state) { - sourceNode(_, state, _) or - sinkNode(_, state, _) or - additionalLocalStateStep(_, state, _, _, _) or - additionalLocalStateStep(_, _, _, state, _) or - additionalJumpStateStep(_, state, _, _, _) or - additionalJumpStateStep(_, _, _, state, _) + private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { + private TypedContent head1; + private TypedContent head2; + private int len; + + AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } + + override TypedContent getHead() { result = head1 } + + override AccessPath getTail() { + Stage5::consCand(head1, result.getApprox()) and + result.getHead() = head2 and + result.length() = len - 1 + } + + override AccessPathFrontHead getFront() { result = TFrontHead(head1) } + + override AccessPathApproxCons getApprox() { + result = TConsCons(head1, head2, len) or + result = TCons1(head1, len) + } + + override int length() { result = len } + + override string toString() { + if len = 2 + then result = "[" + head1.toString() + ", " + head2.toString() + "]" + else + result = + "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" + } } - private newtype TSummaryCtx1 = - TSummaryCtx1None() or - TSummaryCtx1Param(ParamNodeEx p) + private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { + private TypedContent head; + private int len; - private newtype TSummaryCtx2 = - TSummaryCtx2None() or - TSummaryCtx2Some(FlowState s) { relevantState(s) } + AccessPathCons1() { this = TAccessPathCons1(head, len) } - private newtype TSummaryCtx3 = - TSummaryCtx3None() or - TSummaryCtx3Some(PartialAccessPath ap) + override TypedContent getHead() { result = head } - private newtype TRevSummaryCtx1 = - TRevSummaryCtx1None() or - TRevSummaryCtx1Some(ReturnPosition pos) + override AccessPath getTail() { + Stage5::consCand(head, result.getApprox()) and result.length() = len - 1 + } - private newtype TRevSummaryCtx2 = - TRevSummaryCtx2None() or - TRevSummaryCtx2Some(FlowState s) { relevantState(s) } + override AccessPathFrontHead getFront() { result = TFrontHead(head) } - private newtype TRevSummaryCtx3 = - TRevSummaryCtx3None() or - TRevSummaryCtx3Some(RevPartialAccessPath ap) + override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - private newtype TPartialPathNode = - TPartialPathNodeFwd( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = TPartialNil(node.getDataFlowType()) and - exists(config.explorationLimit()) - or - partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap, config) and - distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() - } or - TPartialPathNodeRev( - NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, - RevPartialAccessPath ap, Configuration config - ) { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() and - exists(config.explorationLimit()) - or - revPartialPathStep(_, node, state, sc1, sc2, sc3, ap, config) and - not clearsContentEx(node, ap.getHead()) and + override int length() { result = len } + + override string toString() { + if len = 1 + then result = "[" + head.toString() + "]" + else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" + } + } + + abstract private class PathNodeImpl extends TPathNode { + /** Gets the `FlowState` of this node. */ + abstract FlowState getState(); + + /** Holds if this node is a source. */ + abstract predicate isSource(); + + abstract PathNodeImpl getASuccessorImpl(); + + private PathNodeImpl getASuccessorIfHidden() { + this.isHidden() and + result = this.getASuccessorImpl() + } + + pragma[nomagic] + private PathNodeImpl getANonHiddenSuccessor0() { + result = this.getASuccessorIfHidden*() and + not result.isHidden() + } + + final PathNodeImpl getANonHiddenSuccessor() { + result = this.getASuccessorImpl().getANonHiddenSuccessor0() and + not this.isHidden() + } + + abstract NodeEx getNodeEx(); + + predicate isHidden() { + not Config::includeHiddenNodes() and ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead()) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() + hiddenNode(this.getNodeEx().asNode()) and + not this.isSource() and + not this instanceof PathNodeSink + or + this.getNodeEx() instanceof TNodeImplicitRead + ) } - pragma[nomagic] - private predicate partialPathNodeMk0( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap, config) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - not clearsContentEx(node, ap.getHead().getContent()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead().getContent()) - ) and - if node.asNode() instanceof CastingNode - then compatibleTypes(node.getDataFlowType(), ap.getType()) - else any() - } + string getSourceGroup() { + this.isSource() and + Config::sourceGrouping(this.getNodeEx().asNode(), result) + } + + predicate isFlowSource() { + this.isSource() and not exists(this.getSourceGroup()) + or + this instanceof PathNodeSourceGroup + } + + predicate isFlowSink() { + this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or + this instanceof PathNodeSinkGroup + } + + private string ppAp() { + this instanceof PathNodeSink and result = "" + or + exists(string s | s = this.(PathNodeMid).getAp().toString() | + if s = "" then result = "" else result = " " + s + ) + } + + private string ppCtx() { + this instanceof PathNodeSink and result = "" + or + result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + } - /** - * A `Node` augmented with a call context, an access path, and a configuration. - */ - class PartialPathNode extends TPartialPathNode { /** Gets a textual representation of this element. */ string toString() { result = this.getNodeEx().toString() + this.ppAp() } @@ -4341,305 +3073,339 @@ private module FlowExploration { ) { this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) } + } + + /** Holds if `n` can reach a sink. */ + private predicate directReach(PathNodeImpl n) { + n instanceof PathNodeSink or + n instanceof PathNodeSinkGroup or + directReach(n.getANonHiddenSuccessor()) + } + + /** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ + private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } + + /** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ + private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { + n1.getANonHiddenSuccessor() = n2 and directReach(n2) + } + + private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) + + /** + * A `Node` augmented with a call context (except for sinks) and an access path. + * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. + */ + class PathNode instanceof PathNodeImpl { + PathNode() { reach(this) } + + /** Gets a textual representation of this element. */ + final string toString() { result = super.toString() } + + /** + * Gets a textual representation of this element, including a textual + * representation of the call context. + */ + final string toStringWithContext() { result = super.toStringWithContext() } + + /** + * 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/). + */ + final predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } /** Gets the underlying `Node`. */ - final Node getNode() { this.getNodeEx().projectToNode() = result } + final Node getNode() { super.getNodeEx().projectToNode() = result } - FlowState getState() { none() } - - private NodeEx getNodeEx() { - result = this.(PartialPathNodeFwd).getNodeEx() or - result = this.(PartialPathNodeRev).getNodeEx() - } - - /** Gets the associated configuration. */ - Configuration getConfiguration() { none() } + /** Gets the `FlowState` of this node. */ + final FlowState getState() { result = super.getState() } /** Gets a successor of this node, if any. */ - PartialPathNode getASuccessor() { none() } + final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } - /** - * Gets the approximate distance to the nearest source measured in number - * of interprocedural steps. - */ - int getSourceDistance() { - result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } + /** Holds if this node is a source. */ + final predicate isSource() { super.isSource() } - /** - * Gets the approximate distance to the nearest sink measured in number - * of interprocedural steps. - */ - int getSinkDistance() { - result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } + /** Holds if this node is a grouping of source nodes. */ + final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group) } - private string ppAp() { - exists(string s | - s = this.(PartialPathNodeFwd).getAp().toString() or - s = this.(PartialPathNodeRev).getAp().toString() - | - if s = "" then result = "" else result = " " + s - ) - } - - private string ppCtx() { - result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" - } - - /** Holds if this is a source in a forward-flow path. */ - predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } - - /** Holds if this is a sink in a reverse-flow path. */ - predicate isRevSink() { this.(PartialPathNodeRev).isSink() } + /** Holds if this node is a grouping of sink nodes. */ + final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group) } } /** * Provides the query predicates needed to include a graph in a path-problem query. */ - module PartialPathGraph { + module PathGraph { /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } + query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } + + /** Holds if `n` is a node in the graph of data flow path explanations. */ + query predicate nodes(PathNode n, string key, string val) { + key = "semmle.label" and val = n.toString() + } + + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through + * a subpath between `par` and `ret` with the connecting edges `arg -> par` and + * `ret -> out` is summarized as the edge `arg -> out`. + */ + query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { + Subpaths::subpaths(arg, par, ret, out) + } } - private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { + /** + * An intermediate flow graph node. This is a tuple consisting of a `Node`, + * a `FlowState`, a `CallContext`, a `SummaryCtx`, and an `AccessPath`. + */ + private class PathNodeMid extends PathNodeImpl, TPathNodeMid { NodeEx node; FlowState state; CallContext cc; - TSummaryCtx1 sc1; - TSummaryCtx2 sc2; - TSummaryCtx3 sc3; - PartialAccessPath ap; - Configuration config; + SummaryCtx sc; + AccessPath ap; - PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap, config) } + PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap) } - NodeEx getNodeEx() { result = node } + override NodeEx getNodeEx() { result = node } override FlowState getState() { result = state } CallContext getCallContext() { result = cc } - TSummaryCtx1 getSummaryCtx1() { result = sc1 } + SummaryCtx getSummaryCtx() { result = sc } - TSummaryCtx2 getSummaryCtx2() { result = sc2 } + AccessPath getAp() { result = ap } - TSummaryCtx3 getSummaryCtx3() { result = sc3 } - - PartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeFwd getASuccessor() { - partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp(), - result.getConfiguration()) + private PathNodeMid getSuccMid() { + pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), + result.getSummaryCtx(), result.getAp()) } - predicate isSource() { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap instanceof TPartialNil + override PathNodeImpl getASuccessorImpl() { + // an intermediate step to another intermediate node + result = this.getSuccMid() + or + // a final step to a sink + result = this.getSuccMid().projectToSink() + } + + override predicate isSource() { + sourceNode(node, state) and + ( + if hasSourceCallCtx() + then cc instanceof CallContextSomeCall + else cc instanceof CallContextAny + ) and + sc instanceof SummaryCtxNone and + ap = TAccessPathNil(node.getDataFlowType()) + } + + predicate isAtSink() { + sinkNode(node, state) and + ap instanceof AccessPathNil and + if hasSinkCallCtx() + then + // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` + // is exactly what we need to check. This also implies + // `sc instanceof SummaryCtxNone`. + // For `FeatureEqualSourceSinkCallContext` the initial call context was + // set to `CallContextSomeCall` and jumps are disallowed, so + // `cc instanceof CallContextNoCall` never holds. On the other hand, + // in this case there's never any need to enter a call except to identify + // a summary, so the condition in `pathIntoCallable` enforces this, which + // means that `sc instanceof SummaryCtxNone` holds if and only if we are + // in the call context of the source. + sc instanceof SummaryCtxNone or + cc instanceof CallContextNoCall + else any() + } + + PathNodeSink projectToSink() { + this.isAtSink() and + result.getNodeEx() = node and + result.getState() = state } } - private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { + /** + * A flow graph node corresponding to a sink. This is disjoint from the + * intermediate nodes in order to uniquely correspond to a given sink by + * excluding the `CallContext`. + */ + private class PathNodeSink extends PathNodeImpl, TPathNodeSink { NodeEx node; FlowState state; - TRevSummaryCtx1 sc1; - TRevSummaryCtx2 sc2; - TRevSummaryCtx3 sc3; - RevPartialAccessPath ap; - Configuration config; - PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap, config) } + PathNodeSink() { this = TPathNodeSink(node, state) } - NodeEx getNodeEx() { result = node } + override NodeEx getNodeEx() { result = node } override FlowState getState() { result = state } - TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } + override PathNodeImpl getASuccessorImpl() { result = TPathNodeSinkGroup(this.getSinkGroup()) } - TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } + override predicate isSource() { sourceNode(node, state) } - TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } + string getSinkGroup() { Config::sinkGrouping(node.asNode(), result) } + } - RevPartialAccessPath getAp() { result = ap } + private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { + string sourceGroup; - override Configuration getConfiguration() { result = config } + PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup) } - override PartialPathNodeRev getASuccessor() { - revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), - this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp(), this.getConfiguration()) - } + override NodeEx getNodeEx() { none() } - predicate isSink() { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() + override FlowState getState() { none() } + + override PathNodeImpl getASuccessorImpl() { result.getSourceGroup() = sourceGroup } + + override predicate isSource() { none() } + + override string toString() { result = sourceGroup } + + override 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 } } - private predicate partialPathStep( - PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, - TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config + private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { + string sinkGroup; + + PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup) } + + override NodeEx getNodeEx() { none() } + + override FlowState getState() { none() } + + override PathNodeImpl getASuccessorImpl() { none() } + + override predicate isSource() { none() } + + override string toString() { result = sinkGroup } + + override 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 + } + } + + private predicate pathNode( + PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, + LocalCallContext localCC ) { - not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and - ( - localFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - ) - or - jumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - partialPathStoreStep(mid, _, _, node, ap) and + midnode = mid.getNodeEx() and state = mid.getState() and cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(PartialAccessPath ap0, TypedContent tc | - partialPathReadStep(mid, ap0, tc, node, cc, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsFwd(ap, tc, ap0, config) - ) - or - partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap, config) - or - partialPathOutOfCallable(mid, node, state, cc, ap, config) and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() - or - partialPathThroughCallable(mid, node, state, cc, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() + sc = mid.getSummaryCtx() and + localCC = + getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), + midnode.getEnclosingCallable()) and + ap = mid.getAp() } - bindingset[result, i] - private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } - - pragma[inline] - private predicate partialPathStoreStep( - PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, - PartialAccessPath ap2 + /** + * Holds if data may flow from `mid` to `node`. The last step in or out of + * a callable is recorded by `cc`. + */ + pragma[assume_small_delta] + pragma[nomagic] + private predicate pathStep( + PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap ) { - exists(NodeEx midNode, DataFlowType contentType | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - store(midNode, tc, node, contentType, mid.getConfiguration()) and - ap2.getHead() = tc and - ap2.len() = unbindInt(ap1.len() + 1) and - compatibleTypes(ap1.getType(), contentType) + exists(NodeEx midnode, FlowState state0, LocalCallContext localCC | + pathNode(mid, midnode, state0, cc, sc, ap, localCC) and + localFlowBigStep(midnode, state0, node, state, true, _, localCC) ) + or + exists(AccessPath ap0, NodeEx midnode, FlowState state0, LocalCallContext localCC | + pathNode(mid, midnode, state0, cc, sc, ap0, localCC) and + localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), localCC) and + ap0 instanceof AccessPathNil + ) + or + jumpStepEx(mid.getNodeEx(), node) and + state = mid.getState() and + cc instanceof CallContextAny and + sc instanceof SummaryCtxNone and + ap = mid.getAp() + or + additionalJumpStep(mid.getNodeEx(), node) and + state = mid.getState() and + cc instanceof CallContextAny and + sc instanceof SummaryCtxNone and + mid.getAp() instanceof AccessPathNil and + ap = TAccessPathNil(node.getDataFlowType()) + or + additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state) and + cc instanceof CallContextAny and + sc instanceof SummaryCtxNone and + mid.getAp() instanceof AccessPathNil and + ap = TAccessPathNil(node.getDataFlowType()) + or + exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and + sc = mid.getSummaryCtx() + or + exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and + sc = mid.getSummaryCtx() + or + pathIntoCallable(mid, node, state, _, cc, sc, _) and ap = mid.getAp() + or + pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone + or + pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() } pragma[nomagic] - private predicate apConsFwd( - PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config + private predicate pathReadStep( + PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc ) { - exists(PartialPathNodeFwd mid | - partialPathStoreStep(mid, ap1, tc, _, ap2) and - config = mid.getConfiguration() - ) + ap0 = mid.getAp() and + tc = ap0.getHead() and + Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node) and + state = mid.getState() and + cc = mid.getCallContext() } pragma[nomagic] - private predicate partialPathReadStep( - PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc, - Configuration config + private predicate pathStoreStep( + PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and - ap.getHead() = tc and - pragma[only_bind_into](config) = mid.getConfiguration() and - cc = mid.getCallContext() - ) + ap0 = mid.getAp() and + Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _) and + state = mid.getState() and + cc = mid.getCallContext() } - private predicate partialPathOutOfCallable0( - PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, - PartialAccessPath ap, Configuration config + private predicate pathOutOfCallable0( + PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa ) { pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and state = mid.getState() and innercc = mid.getCallContext() and innercc instanceof CallContextNoCall and - ap = mid.getAp() and - config = mid.getConfiguration() + apa = mid.getAp().getApprox() } pragma[nomagic] - private predicate partialPathOutOfCallable1( - PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config + private predicate pathOutOfCallable1( + PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, + AccessPathApprox apa ) { exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - partialPathOutOfCallable0(mid, pos, state, innercc, ap, config) and + pathOutOfCallable0(mid, pos, state, innercc, apa) and c = pos.getCallable() and kind = pos.getKind() and resolveReturn(innercc, c, call) @@ -4648,53 +3414,86 @@ private module FlowExploration { ) } - private predicate partialPathOutOfCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(ReturnKindExt kind, DataFlowCall call | - partialPathOutOfCallable1(mid, call, kind, state, cc, ap, config) - | - out.asNode() = kind.getAnOutNode(call) + pragma[noinline] + private NodeEx getAnOutNodeFlow(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa) { + result.asNode() = kind.getAnOutNode(call) and + Stage5::revFlow(result, _, apa) + } + + /** + * Holds if data may flow from `mid` to `out`. The last step of this path + * is a return from a callable and is recorded by `cc`, if needed. + */ + pragma[noinline] + private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { + exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa | + pathOutOfCallable1(mid, call, kind, state, cc, apa) and + out = getAnOutNodeFlow(kind, call, apa) ) } + /** + * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. + */ pragma[noinline] - private predicate partialPathIntoArg( - PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, - DataFlowCall call, PartialAccessPath ap, Configuration config + private predicate pathIntoArg( + PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, + AccessPath ap, AccessPathApprox apa ) { - exists(ArgNode arg, ArgumentPosition apos | - arg = mid.getNodeEx().asNode() and - state = mid.getState() and - cc = mid.getCallContext() and - arg.argumentOf(call, apos) and - ap = mid.getAp() and - config = mid.getConfiguration() and + exists(ArgNodeEx arg, ArgumentPosition apos | + pathNode(mid, arg, state, cc, _, ap, _) and + arg.asNode().(ArgNode).argumentOf(call, apos) and + apa = ap.getApprox() and parameterMatch(ppos, apos) ) } pragma[nomagic] - private predicate partialPathIntoCallable0( - PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, PartialAccessPath ap, Configuration config + private predicate parameterCand( + DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa ) { - partialPathIntoArg(mid, pos, state, outercc, call, ap, config) and - callable = resolveCall(call, outercc) + exists(ParamNodeEx p | + Stage5::revFlow(p, _, apa) and + p.isParameterOf(callable, pos) + ) } - private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, - CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, - DataFlowCall call, PartialAccessPath ap, Configuration config + pragma[nomagic] + private predicate pathIntoCallable0( + PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, + CallContext outercc, DataFlowCall call, AccessPath ap ) { - exists(ParameterPosition pos, DataFlowCallable callable | - partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and + exists(AccessPathApprox apa | + pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, + pragma[only_bind_into](apa)) and + callable = resolveCall(call, outercc) and + parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa)) + ) + } + + /** + * Holds if data may flow from `mid` to `p` through `call`. The contexts + * before and after entering the callable are `outercc` and `innercc`, + * respectively. + */ + pragma[nomagic] + private predicate pathIntoCallable( + PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, + SummaryCtx sc, DataFlowCall call + ) { + exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | + pathIntoCallable0(mid, callable, pos, state, outercc, call, ap) and p.isParameterOf(callable, pos) and - sc1 = TSummaryCtx1Param(p) and - sc2 = TSummaryCtx2Some(state) and - sc3 = TSummaryCtx3Some(ap) + ( + sc = TSummaryCtxSome(p, state, ap) + or + not exists(TSummaryCtxSome(p, state, ap)) and + sc = TSummaryCtxNone() and + // When the call contexts of source and sink needs to match then there's + // never any reason to enter a callable except to find a summary. See also + // the comment in `PathNodeMid::isAtSink`. + not Config::getAFeature() instanceof FeatureEqualSourceSinkCallContext + ) | if recordDataFlowCallSite(call, callable) then innercc = TSpecificCall(call) @@ -4702,252 +3501,1108 @@ private module FlowExploration { ) } + /** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ pragma[nomagic] - private predicate paramFlowsThroughInPartialPath( - ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config + private predicate paramFlowsThrough( + ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, + AccessPathApprox apa ) { - exists(PartialPathNodeFwd mid, RetNodeEx ret | - mid.getNodeEx() = ret and + exists(RetNodeEx ret | + pathNode(_, ret, state, cc, sc, ap, _) and kind = ret.getKind() and + apa = ap.getApprox() and + parameterFlowThroughAllowed(sc.getParamNode(), kind) + ) + } + + pragma[assume_small_delta] + pragma[nomagic] + private predicate pathThroughCallable0( + DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, + AccessPath ap, AccessPathApprox apa + ) { + exists(CallContext innercc, SummaryCtx sc | + pathIntoCallable(mid, _, _, cc, innercc, sc, call) and + paramFlowsThrough(kind, state, innercc, sc, ap, apa) + ) + } + + /** + * Holds if data may flow from `mid` through a callable to the node `out`. + * The context `cc` is restored to its value prior to entering the callable. + */ + pragma[noinline] + private predicate pathThroughCallable( + PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap + ) { + exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa | + pathThroughCallable0(call, mid, kind, state, cc, ap, apa) and + out = getAnOutNodeFlow(kind, call, apa) + ) + } + + private module Subpaths { + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by + * `kind`, `sc`, `apout`, and `innercc`. + */ + pragma[nomagic] + private predicate subpaths01( + PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, + NodeEx out, FlowState sout, AccessPath apout + ) { + pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and + pathIntoCallable(arg, par, _, _, innercc, sc, _) and + paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, + pragma[only_bind_into](apout), _) and + not arg.isHidden() + } + + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by + * `kind`, `sc`, `sout`, `apout`, and `innercc`. + */ + pragma[nomagic] + private predicate subpaths02( + PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, + NodeEx out, FlowState sout, AccessPath apout + ) { + subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and + out.asNode() = kind.getAnOutNode(_) + } + + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple. + */ + pragma[nomagic] + private predicate subpaths03( + PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, + AccessPath apout + ) { + exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | + subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and + pathNode(ret, retnode, sout, innercc, sc, apout, _) and + kind = retnode.getKind() + ) + } + + private PathNodeImpl localStepToHidden(PathNodeImpl n) { + n.getASuccessorImpl() = result and + result.isHidden() and + exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | + localFlowBigStep(n1, _, n2, _, _, _, _) or + storeEx(n1, _, n2, _) or + readSetEx(n1, _, n2) + ) + } + + pragma[nomagic] + private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { + succ = pred.getANonHiddenSuccessor() and + succNode = succ.getNodeEx() + } + + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through + * a subpath between `par` and `ret` with the connecting edges `arg -> par` and + * `ret -> out` is summarized as the edge `arg -> out`. + */ + predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { + exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | + pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and + subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and + hasSuccessor(pragma[only_bind_into](arg), par, p) and + not ret.isHidden() and + pathNode(out0, o, sout, _, _, apout, _) + | + out = out0 or out = out0.projectToSink() + ) + } + + /** + * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. + */ + predicate retReach(PathNodeImpl n) { + exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) + or + exists(PathNodeImpl mid | + retReach(mid) and + n.getANonHiddenSuccessor() = mid and + not subpaths(_, mid, _, _) + ) + } + } + + /** + * Holds if data can flow from `source` to `sink`. + * + * The corresponding paths are generated from the end-points and the graph + * included in the module `PathGraph`. + */ + predicate hasFlowPath(PathNode source, PathNode sink) { + exists(PathNodeImpl flowsource, PathNodeImpl flowsink | + source = flowsource and sink = flowsink + | + flowsource.isFlowSource() and + (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and + flowsink.isFlowSink() + ) + } + + private predicate flowsTo(PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink) { + flowsource.isSource() and + flowsource.getNodeEx().asNode() = source and + (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and + flowsink.getNodeEx().asNode() = sink + } + + /** + * Holds if data can flow from `source` to `sink`. + */ + predicate hasFlow(Node source, Node sink) { flowsTo(_, _, source, sink) } + + /** + * Holds if data can flow from some source to `sink`. + */ + predicate hasFlowTo(Node sink) { sink = any(PathNodeSink n).getNodeEx().asNode() } + + /** + * Holds if data can flow from some source to `sink`. + */ + predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) } + + private predicate finalStats( + boolean fwd, int nodes, int fields, int conscand, int states, int tuples + ) { + fwd = true and + nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and + fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and + conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and + states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and + tuples = count(PathNodeImpl pn) + or + fwd = false and + nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and + fields = + count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and + conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and + states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and + tuples = count(PathNode pn) + } + + /** + * INTERNAL: Only for debugging. + * + * Calculates per-stage metrics for data flow. + */ + predicate stageStats( + int n, string stage, int nodes, int fields, int conscand, int states, int tuples + ) { + stage = "1 Fwd" and + n = 10 and + Stage1::stats(true, nodes, fields, conscand, states, tuples) + or + stage = "1 Rev" and + n = 15 and + Stage1::stats(false, nodes, fields, conscand, states, tuples) + or + stage = "2 Fwd" and + n = 20 and + Stage2::stats(true, nodes, fields, conscand, states, tuples) + or + stage = "2 Rev" and + n = 25 and + Stage2::stats(false, nodes, fields, conscand, states, tuples) + or + stage = "3 Fwd" and + n = 30 and + Stage3::stats(true, nodes, fields, conscand, states, tuples) + or + stage = "3 Rev" and + n = 35 and + Stage3::stats(false, nodes, fields, conscand, states, tuples) + or + stage = "4 Fwd" and + n = 40 and + Stage4::stats(true, nodes, fields, conscand, states, tuples) + or + stage = "4 Rev" and + n = 45 and + Stage4::stats(false, nodes, fields, conscand, states, tuples) + or + stage = "5 Fwd" and + n = 50 and + Stage5::stats(true, nodes, fields, conscand, states, tuples) + or + stage = "5 Rev" and + n = 55 and + Stage5::stats(false, nodes, fields, conscand, states, tuples) + or + stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) + or + stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) + } + + module FlowExploration { + private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2) { + exists(NodeEx node1, NodeEx node2 | + jumpStepEx(node1, node2) + or + additionalJumpStep(node1, node2) + or + additionalJumpStateStep(node1, _, node2, _) + or + // flow into callable + viableParamArgEx(_, node2, node1) + or + // flow out of a callable + viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) + | + c1 = node1.getEnclosingCallable() and + c2 = node2.getEnclosingCallable() and + c1 != c2 + ) + } + + private predicate interestingCallableSrc(DataFlowCallable c) { + exists(Node n | Config::isSource(n, _) and c = getNodeEnclosingCallable(n)) + or + exists(DataFlowCallable mid | interestingCallableSrc(mid) and callableStep(mid, c)) + } + + private predicate interestingCallableSink(DataFlowCallable c) { + exists(Node n | Config::isSink(n, _) and c = getNodeEnclosingCallable(n)) + or + exists(DataFlowCallable mid | interestingCallableSink(mid) and callableStep(c, mid)) + } + + private newtype TCallableExt = + TCallable(DataFlowCallable c) { + interestingCallableSrc(c) or + interestingCallableSink(c) + } or + TCallableSrc() or + TCallableSink() + + private predicate callableExtSrc(TCallableSrc src) { any() } + + private predicate callableExtSink(TCallableSink sink) { any() } + + private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { + exists(DataFlowCallable c1, DataFlowCallable c2 | + callableStep(c1, c2) and + ce1 = TCallable(c1) and + ce2 = TCallable(c2) + ) + or + exists(Node n | + ce1 = TCallableSrc() and + Config::isSource(n, _) and + ce2 = TCallable(getNodeEnclosingCallable(n)) + ) + or + exists(Node n | + ce2 = TCallableSink() and + Config::isSink(n, _) and + ce1 = TCallable(getNodeEnclosingCallable(n)) + ) + } + + private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { + callableExtStepFwd(ce2, ce1) + } + + private int distSrcExt(TCallableExt c) = + shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) + + private int distSinkExt(TCallableExt c) = + shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) + + private int distSrc(DataFlowCallable c) { result = distSrcExt(TCallable(c)) - 1 } + + private int distSink(DataFlowCallable c) { result = distSinkExt(TCallable(c)) - 1 } + + private newtype TPartialAccessPath = + TPartialNil(DataFlowType t) or + TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } + + /** + * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first + * element of the list and its length are tracked. If data flows from a source to + * a given node with a given `AccessPath`, this indicates the sequence of + * dereference operations needed to get from the value in the node to the + * tracked object. The final type indicates the type of the tracked object. + */ + private class PartialAccessPath extends TPartialAccessPath { + abstract string toString(); + + TypedContent getHead() { this = TPartialCons(result, _) } + + int len() { + this = TPartialNil(_) and result = 0 + or + this = TPartialCons(_, result) + } + + DataFlowType getType() { + this = TPartialNil(result) + or + exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) + } + } + + private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { + override string toString() { + exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) + } + } + + private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { + override string toString() { + exists(TypedContent tc, int len | this = TPartialCons(tc, len) | + if len = 1 + then result = "[" + tc.toString() + "]" + else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" + ) + } + } + + private newtype TRevPartialAccessPath = + TRevPartialNil() or + TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } + + /** + * Conceptually a list of `Content`s, but only the first + * element of the list and its length are tracked. + */ + private class RevPartialAccessPath extends TRevPartialAccessPath { + abstract string toString(); + + Content getHead() { this = TRevPartialCons(result, _) } + + int len() { + this = TRevPartialNil() and result = 0 + or + this = TRevPartialCons(_, result) + } + } + + private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { + override string toString() { result = "" } + } + + private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { + override string toString() { + exists(Content c, int len | this = TRevPartialCons(c, len) | + if len = 1 + then result = "[" + c.toString() + "]" + else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" + ) + } + } + + private predicate relevantState(FlowState state) { + sourceNode(_, state) or + sinkNode(_, state) or + additionalLocalStateStep(_, state, _, _) or + additionalLocalStateStep(_, _, _, state) or + additionalJumpStateStep(_, state, _, _) or + additionalJumpStateStep(_, _, _, state) + } + + private newtype TSummaryCtx1 = + TSummaryCtx1None() or + TSummaryCtx1Param(ParamNodeEx p) + + private newtype TSummaryCtx2 = + TSummaryCtx2None() or + TSummaryCtx2Some(FlowState s) { relevantState(s) } + + private newtype TSummaryCtx3 = + TSummaryCtx3None() or + TSummaryCtx3Some(PartialAccessPath ap) + + private newtype TRevSummaryCtx1 = + TRevSummaryCtx1None() or + TRevSummaryCtx1Some(ReturnPosition pos) + + private newtype TRevSummaryCtx2 = + TRevSummaryCtx2None() or + TRevSummaryCtx2Some(FlowState s) { relevantState(s) } + + private newtype TRevSummaryCtx3 = + TRevSummaryCtx3None() or + TRevSummaryCtx3Some(RevPartialAccessPath ap) + + private newtype TPartialPathNode = + TPartialPathNodeFwd( + NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, + TSummaryCtx3 sc3, PartialAccessPath ap + ) { + sourceNode(node, state) and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() and + ap = TPartialNil(node.getDataFlowType()) and + exists(explorationLimit()) + or + partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap) and + distSrc(node.getEnclosingCallable()) <= explorationLimit() + } or + TPartialPathNodeRev( + NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, + RevPartialAccessPath ap + ) { + sinkNode(node, state) and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + sc3 = TRevSummaryCtx3None() and + ap = TRevPartialNil() and + exists(explorationLimit()) + or + revPartialPathStep(_, node, state, sc1, sc2, sc3, ap) and + not clearsContentEx(node, ap.getHead()) and + ( + notExpectsContent(node) or + expectsContentEx(node, ap.getHead()) + ) and + not fullBarrier(node) and + not stateBarrier(node, state) and + distSink(node.getEnclosingCallable()) <= explorationLimit() + } + + pragma[nomagic] + private predicate partialPathNodeMk0( + NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, + TSummaryCtx3 sc3, PartialAccessPath ap + ) { + partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap) and + not fullBarrier(node) and + not stateBarrier(node, state) and + not clearsContentEx(node, ap.getHead().getContent()) and + ( + notExpectsContent(node) or + expectsContentEx(node, ap.getHead().getContent()) + ) and + if node.asNode() instanceof CastingNode + then compatibleTypes(node.getDataFlowType(), ap.getType()) + else any() + } + + /** + * A `Node` augmented with a call context, an access path, and a configuration. + */ + class PartialPathNode extends TPartialPathNode { + /** Gets a textual representation of this element. */ + string toString() { result = this.getNodeEx().toString() + this.ppAp() } + + /** + * Gets a textual representation of this element, including a textual + * representation of the call context. + */ + string toStringWithContext() { + result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() + } + + /** + * 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 + ) { + this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + + /** Gets the underlying `Node`. */ + final Node getNode() { this.getNodeEx().projectToNode() = result } + + FlowState getState() { none() } + + private NodeEx getNodeEx() { + result = this.(PartialPathNodeFwd).getNodeEx() or + result = this.(PartialPathNodeRev).getNodeEx() + } + + /** Gets a successor of this node, if any. */ + PartialPathNode getASuccessor() { none() } + + /** + * Gets the approximate distance to the nearest source measured in number + * of interprocedural steps. + */ + int getSourceDistance() { result = distSrc(this.getNodeEx().getEnclosingCallable()) } + + /** + * Gets the approximate distance to the nearest sink measured in number + * of interprocedural steps. + */ + int getSinkDistance() { result = distSink(this.getNodeEx().getEnclosingCallable()) } + + private string ppAp() { + exists(string s | + s = this.(PartialPathNodeFwd).getAp().toString() or + s = this.(PartialPathNodeRev).getAp().toString() + | + if s = "" then result = "" else result = " " + s + ) + } + + private string ppCtx() { + result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" + } + + /** Holds if this is a source in a forward-flow path. */ + predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } + + /** Holds if this is a sink in a reverse-flow path. */ + predicate isRevSink() { this.(PartialPathNodeRev).isSink() } + } + + /** + * Provides the query predicates needed to include a graph in a path-problem query. + */ + module PartialPathGraph { + /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ + query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } + } + + private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { + NodeEx node; + FlowState state; + CallContext cc; + TSummaryCtx1 sc1; + TSummaryCtx2 sc2; + TSummaryCtx3 sc3; + PartialAccessPath ap; + + PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap) } + + NodeEx getNodeEx() { result = node } + + override FlowState getState() { result = state } + + CallContext getCallContext() { result = cc } + + TSummaryCtx1 getSummaryCtx1() { result = sc1 } + + TSummaryCtx2 getSummaryCtx2() { result = sc2 } + + TSummaryCtx3 getSummaryCtx3() { result = sc3 } + + PartialAccessPath getAp() { result = ap } + + override PartialPathNodeFwd getASuccessor() { + partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), + result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp()) + } + + predicate isSource() { + sourceNode(node, state) and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() and + ap instanceof TPartialNil + } + } + + private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { + NodeEx node; + FlowState state; + TRevSummaryCtx1 sc1; + TRevSummaryCtx2 sc2; + TRevSummaryCtx3 sc3; + RevPartialAccessPath ap; + + PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap) } + + NodeEx getNodeEx() { result = node } + + override FlowState getState() { result = state } + + TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } + + TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } + + TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } + + RevPartialAccessPath getAp() { result = ap } + + override PartialPathNodeRev getASuccessor() { + revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), + this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp()) + } + + predicate isSink() { + sinkNode(node, state) and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + sc3 = TRevSummaryCtx3None() and + ap = TRevPartialNil() + } + } + + private predicate partialPathStep( + PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, + TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap + ) { + not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and + ( + localFlowStepEx(mid.getNodeEx(), node) and + state = mid.getState() and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + ap = mid.getAp() + or + additionalLocalFlowStep(mid.getNodeEx(), node) and + state = mid.getState() and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + mid.getAp() instanceof PartialAccessPathNil and + ap = TPartialNil(node.getDataFlowType()) + or + additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state) and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + mid.getAp() instanceof PartialAccessPathNil and + ap = TPartialNil(node.getDataFlowType()) + ) + or + jumpStepEx(mid.getNodeEx(), node) and + state = mid.getState() and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() and + ap = mid.getAp() + or + additionalJumpStep(mid.getNodeEx(), node) and + state = mid.getState() and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() and + mid.getAp() instanceof PartialAccessPathNil and + ap = TPartialNil(node.getDataFlowType()) + or + additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state) and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() and + mid.getAp() instanceof PartialAccessPathNil and + ap = TPartialNil(node.getDataFlowType()) + or + partialPathStoreStep(mid, _, _, node, ap) and state = mid.getState() and cc = mid.getCallContext() and sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() and + sc3 = mid.getSummaryCtx3() + or + exists(PartialAccessPath ap0, TypedContent tc | + partialPathReadStep(mid, ap0, tc, node, cc) and + state = mid.getState() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + apConsFwd(ap, tc, ap0) + ) + or + partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap) + or + partialPathOutOfCallable(mid, node, state, cc, ap) and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() + or + partialPathThroughCallable(mid, node, state, cc, ap) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() + } + + bindingset[result, i] + private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } + + pragma[inline] + private predicate partialPathStoreStep( + PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, + PartialAccessPath ap2 + ) { + exists(NodeEx midNode, DataFlowType contentType | + midNode = mid.getNodeEx() and + ap1 = mid.getAp() and + storeEx(midNode, tc, node, contentType) and + ap2.getHead() = tc and + ap2.len() = unbindInt(ap1.len() + 1) and + compatibleTypes(ap1.getType(), contentType) + ) + } + + pragma[nomagic] + private predicate apConsFwd(PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2) { + partialPathStoreStep(_, ap1, tc, _, ap2) + } + + pragma[nomagic] + private predicate partialPathReadStep( + PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc + ) { + exists(NodeEx midNode | + midNode = mid.getNodeEx() and + ap = mid.getAp() and + read(midNode, tc.getContent(), node) and + ap.getHead() = tc and + cc = mid.getCallContext() + ) + } + + private predicate partialPathOutOfCallable0( + PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, + PartialAccessPath ap + ) { + pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and + state = mid.getState() and + innercc = mid.getCallContext() and + innercc instanceof CallContextNoCall and ap = mid.getAp() - ) - } + } - pragma[noinline] - private predicate partialPathThroughCallable0( - DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | - partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _, config) and - paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap, config) - ) - } + pragma[nomagic] + private predicate partialPathOutOfCallable1( + PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, + CallContext cc, PartialAccessPath ap + ) { + exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | + partialPathOutOfCallable0(mid, pos, state, innercc, ap) and + c = pos.getCallable() and + kind = pos.getKind() and + resolveReturn(innercc, c, call) + | + if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + ) + } - private predicate partialPathThroughCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ReturnKindExt kind | - partialPathThroughCallable0(call, mid, kind, state, cc, ap, config) and - out.asNode() = kind.getAnOutNode(call) - ) - } + private predicate partialPathOutOfCallable( + PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap + ) { + exists(ReturnKindExt kind, DataFlowCall call | + partialPathOutOfCallable1(mid, call, kind, state, cc, ap) + | + out.asNode() = kind.getAnOutNode(call) + ) + } - pragma[nomagic] - private predicate revPartialPathStep( - PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, - TRevSummaryCtx3 sc3, RevPartialAccessPath ap, Configuration config - ) { - localFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - jumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - revPartialPathReadStep(mid, _, _, node, ap) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(RevPartialAccessPath ap0, Content c | - revPartialPathStoreStep(mid, ap0, c, node, config) and + pragma[noinline] + private predicate partialPathIntoArg( + PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, + DataFlowCall call, PartialAccessPath ap + ) { + exists(ArgNode arg, ArgumentPosition apos | + arg = mid.getNodeEx().asNode() and + state = mid.getState() and + cc = mid.getCallContext() and + arg.argumentOf(call, apos) and + ap = mid.getAp() and + parameterMatch(ppos, apos) + ) + } + + pragma[nomagic] + private predicate partialPathIntoCallable0( + PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, + CallContext outercc, DataFlowCall call, PartialAccessPath ap + ) { + partialPathIntoArg(mid, pos, state, outercc, call, ap) and + callable = resolveCall(call, outercc) + } + + private predicate partialPathIntoCallable( + PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, + CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, + DataFlowCall call, PartialAccessPath ap + ) { + exists(ParameterPosition pos, DataFlowCallable callable | + partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap) and + p.isParameterOf(callable, pos) and + sc1 = TSummaryCtx1Param(p) and + sc2 = TSummaryCtx2Some(state) and + sc3 = TSummaryCtx3Some(ap) + | + if recordDataFlowCallSite(call, callable) + then innercc = TSpecificCall(call) + else innercc = TSomeCall() + ) + } + + pragma[nomagic] + private predicate paramFlowsThroughInPartialPath( + ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, + TSummaryCtx3 sc3, PartialAccessPath ap + ) { + exists(PartialPathNodeFwd mid, RetNodeEx ret | + mid.getNodeEx() = ret and + kind = ret.getKind() and + state = mid.getState() and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + ap = mid.getAp() + ) + } + + pragma[noinline] + private predicate partialPathThroughCallable0( + DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, + CallContext cc, PartialAccessPath ap + ) { + exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | + partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _) and + paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap) + ) + } + + private predicate partialPathThroughCallable( + PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap + ) { + exists(DataFlowCall call, ReturnKindExt kind | + partialPathThroughCallable0(call, mid, kind, state, cc, ap) and + out.asNode() = kind.getAnOutNode(call) + ) + } + + pragma[nomagic] + private predicate revPartialPathStep( + PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, + TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, RevPartialAccessPath ap + ) { + localFlowStepEx(node, mid.getNodeEx()) and state = mid.getState() and sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and sc3 = mid.getSummaryCtx3() and - apConsRev(ap, c, ap0, config) - ) - or - exists(ParamNodeEx p | - mid.getNodeEx() = p and - viableParamArgEx(_, p, node) and + ap = mid.getAp() + or + additionalLocalFlowStep(node, mid.getNodeEx()) and state = mid.getState() and sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and sc3 = mid.getSummaryCtx3() and + mid.getAp() instanceof RevPartialAccessPathNil and + ap = TRevPartialNil() + or + additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState()) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + mid.getAp() instanceof RevPartialAccessPathNil and + ap = TRevPartialNil() + or + jumpStepEx(node, mid.getNodeEx()) and + state = mid.getState() and sc1 = TRevSummaryCtx1None() and sc2 = TRevSummaryCtx2None() and sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - or - exists(ReturnPosition pos | - revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap, config) and - pos = getReturnPosition(node.asNode()) - ) - or - revPartialPathThroughCallable(mid, node, state, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - pragma[inline] - private predicate revPartialPathReadStep( - PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, - RevPartialAccessPath ap2 - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - read(node, c, midNode, mid.getConfiguration()) and - ap2.getHead() = c and - ap2.len() = unbindInt(ap1.len() + 1) - ) - } - - pragma[nomagic] - private predicate apConsRev( - RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeRev mid | - revPartialPathReadStep(mid, ap1, c, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathStoreStep( - PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config - ) { - exists(NodeEx midNode, TypedContent tc | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - store(node, tc, midNode, _, config) and - ap.getHead() = c and - config = mid.getConfiguration() and - tc.getContent() = c - ) - } - - pragma[nomagic] - private predicate revPartialPathIntoReturn( - PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, - TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap, - Configuration config - ) { - exists(NodeEx out | - mid.getNodeEx() = out and - mid.getState() = state and - viableReturnPosOutEx(call, pos, out) and - sc1 = TRevSummaryCtx1Some(pos) and - sc2 = TRevSummaryCtx2Some(state) and - sc3 = TRevSummaryCtx3Some(ap) and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathFlowsThrough( - ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, - TRevSummaryCtx3Some sc3, RevPartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | - mid.getNodeEx() = p and - mid.getState() = state and - p.getPosition() = ppos and + ap = mid.getAp() + or + additionalJumpStep(node, mid.getNodeEx()) and + state = mid.getState() and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + sc3 = TRevSummaryCtx3None() and + mid.getAp() instanceof RevPartialAccessPathNil and + ap = TRevPartialNil() + or + additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState()) and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + sc3 = TRevSummaryCtx3None() and + mid.getAp() instanceof RevPartialAccessPathNil and + ap = TRevPartialNil() + or + revPartialPathReadStep(mid, _, _, node, ap) and + state = mid.getState() and sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } + sc3 = mid.getSummaryCtx3() + or + exists(RevPartialAccessPath ap0, Content c | + revPartialPathStoreStep(mid, ap0, c, node) and + state = mid.getState() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + apConsRev(ap, c, ap0) + ) + or + exists(ParamNodeEx p | + mid.getNodeEx() = p and + viableParamArgEx(_, p, node) and + state = mid.getState() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + sc3 = TRevSummaryCtx3None() and + ap = mid.getAp() + ) + or + exists(ReturnPosition pos | + revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap) and + pos = getReturnPosition(node.asNode()) + ) + or + revPartialPathThroughCallable(mid, node, state, ap) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() + } - pragma[nomagic] - private predicate revPartialPathThroughCallable0( - DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, - RevPartialAccessPath ap, Configuration config - ) { - exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | - revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _, config) and - revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap, config) - ) - } + pragma[inline] + private predicate revPartialPathReadStep( + PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, + RevPartialAccessPath ap2 + ) { + exists(NodeEx midNode | + midNode = mid.getNodeEx() and + ap1 = mid.getAp() and + read(node, c, midNode) and + ap2.getHead() = c and + ap2.len() = unbindInt(ap1.len() + 1) + ) + } - pragma[nomagic] - private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ArgumentPosition pos | - revPartialPathThroughCallable0(call, mid, pos, state, ap, config) and - node.asNode().(ArgNode).argumentOf(call, pos) - ) + pragma[nomagic] + private predicate apConsRev(RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2) { + revPartialPathReadStep(_, ap1, c, _, ap2) + } + + pragma[nomagic] + private predicate revPartialPathStoreStep( + PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node + ) { + exists(NodeEx midNode, TypedContent tc | + midNode = mid.getNodeEx() and + ap = mid.getAp() and + storeEx(node, tc, midNode, _) and + ap.getHead() = c and + tc.getContent() = c + ) + } + + pragma[nomagic] + private predicate revPartialPathIntoReturn( + PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, + TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap + ) { + exists(NodeEx out | + mid.getNodeEx() = out and + mid.getState() = state and + viableReturnPosOutEx(call, pos, out) and + sc1 = TRevSummaryCtx1Some(pos) and + sc2 = TRevSummaryCtx2Some(state) and + sc3 = TRevSummaryCtx3Some(ap) and + ap = mid.getAp() + ) + } + + pragma[nomagic] + private predicate revPartialPathFlowsThrough( + ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, + TRevSummaryCtx3Some sc3, RevPartialAccessPath ap + ) { + exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | + mid.getNodeEx() = p and + mid.getState() = state and + p.getPosition() = ppos and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + ap = mid.getAp() and + parameterMatch(ppos, apos) + ) + } + + pragma[nomagic] + private predicate revPartialPathThroughCallable0( + DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, + RevPartialAccessPath ap + ) { + exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | + revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _) and + revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap) + ) + } + + pragma[nomagic] + private predicate revPartialPathThroughCallable( + PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap + ) { + exists(DataFlowCall call, ArgumentPosition pos | + revPartialPathThroughCallable0(call, mid, pos, state, ap) and + node.asNode().(ArgNode).argumentOf(call, pos) + ) + } + + private predicate partialFlow(PartialPathNode source, PartialPathNode node) { + source.isFwdSource() and + node = source.getASuccessor+() + } + + private predicate revPartialFlow(PartialPathNode node, PartialPathNode sink) { + sink.isRevSink() and + node.getASuccessor+() = sink + } + + /** + * Holds if there is a partial data flow path from `source` to `node`. The + * approximate distance between `node` and the closest source is `dist` and + * is restricted to be less than or equal to `explorationLimit()`. This + * predicate completely disregards sink definitions. + * + * This predicate is intended for data-flow exploration and debugging and may + * perform poorly if the number of sources is too big and/or the exploration + * limit is set too high without using barriers. + * + * To use this in a `path-problem` query, import the module `PartialPathGraph`. + */ + predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { + partialFlow(source, node) and + dist = node.getSourceDistance() + } + + /** + * Holds if there is a partial data flow path from `node` to `sink`. The + * approximate distance between `node` and the closest sink is `dist` and + * is restricted to be less than or equal to `explorationLimit()`. This + * predicate completely disregards source definitions. + * + * This predicate is intended for data-flow exploration and debugging and may + * perform poorly if the number of sinks is too big and/or the exploration + * limit is set too high without using barriers. + * + * To use this in a `path-problem` query, import the module `PartialPathGraph`. + * + * Note that reverse flow has slightly lower precision than the corresponding + * forward flow, as reverse flow disregards type pruning among other features. + */ + predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { + revPartialFlow(node, sink) and + dist = node.getSinkDistance() + } } } - -import FlowExploration - -private predicate partialFlow( - PartialPathNode source, PartialPathNode node, Configuration configuration -) { - source.getConfiguration() = configuration and - source.isFwdSource() and - node = source.getASuccessor+() -} - -private predicate revPartialFlow( - PartialPathNode node, PartialPathNode sink, Configuration configuration -) { - sink.getConfiguration() = configuration and - sink.isRevSink() and - node.getASuccessor+() = sink -} diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl1.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl1.qll new file mode 100644 index 00000000000..e6bdc74cceb --- /dev/null +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl1.qll @@ -0,0 +1,396 @@ +/** + * DEPRECATED: Use `Make` and `MakeWithState` instead. + * + * Provides a `Configuration` class backwards-compatible interface to the data + * flow library. + */ + +private import DataFlowImplCommon +private import DataFlowImplSpecific::Private +import DataFlowImplSpecific::Public +private import DataFlowImpl +import DataFlowImplCommonPublic +import FlowStateString + +/** + * A configuration of interprocedural data flow analysis. This defines + * sources, sinks, and any other configurable aspect of the analysis. Each + * use of the global data flow library must define its own unique extension + * of this abstract class. To create a configuration, extend this class with + * a subclass whose characteristic predicate is a unique singleton string. + * For example, write + * + * ```ql + * class MyAnalysisConfiguration extends DataFlow::Configuration { + * MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" } + * // Override `isSource` and `isSink`. + * // Optionally override `isBarrier`. + * // Optionally override `isAdditionalFlowStep`. + * } + * ``` + * Conceptually, this defines a graph where the nodes are `DataFlow::Node`s and + * the edges are those data-flow steps that preserve the value of the node + * along with any additional edges defined by `isAdditionalFlowStep`. + * Specifying nodes in `isBarrier` will remove those nodes from the graph, and + * specifying nodes in `isBarrierIn` and/or `isBarrierOut` will remove in-going + * and/or out-going edges from those nodes, respectively. + * + * Then, to query whether there is flow between some `source` and `sink`, + * write + * + * ```ql + * exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink)) + * ``` + * + * Multiple configurations can coexist, but two classes extending + * `DataFlow::Configuration` should never depend on each other. One of them + * should instead depend on a `DataFlow2::Configuration`, a + * `DataFlow3::Configuration`, or a `DataFlow4::Configuration`. + */ +abstract class Configuration extends string { + bindingset[this] + Configuration() { any() } + + /** + * Holds if `source` is a relevant data flow source. + */ + predicate isSource(Node source) { none() } + + /** + * Holds if `source` is a relevant data flow source with the given initial + * `state`. + */ + predicate isSource(Node source, FlowState state) { none() } + + /** + * Holds if `sink` is a relevant data flow sink. + */ + predicate isSink(Node sink) { none() } + + /** + * Holds if `sink` is a relevant data flow sink accepting `state`. + */ + predicate isSink(Node sink, FlowState state) { none() } + + /** + * Holds if data flow through `node` is prohibited. This completely removes + * `node` from the data flow graph. + */ + predicate isBarrier(Node node) { none() } + + /** + * Holds if data flow through `node` is prohibited when the flow state is + * `state`. + */ + predicate isBarrier(Node node, FlowState state) { none() } + + /** Holds if data flow into `node` is prohibited. */ + predicate isBarrierIn(Node node) { none() } + + /** Holds if data flow out of `node` is prohibited. */ + predicate isBarrierOut(Node node) { none() } + + /** + * DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead. + * + * Holds if data flow through nodes guarded by `guard` is prohibited. + */ + deprecated predicate isBarrierGuard(BarrierGuard guard) { none() } + + /** + * DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead. + * + * Holds if data flow through nodes guarded by `guard` is prohibited when + * the flow state is `state` + */ + deprecated predicate isBarrierGuard(BarrierGuard guard, FlowState state) { none() } + + /** + * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. + */ + predicate isAdditionalFlowStep(Node node1, Node node2) { none() } + + /** + * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. + * This step is only applicable in `state1` and updates the flow state to `state2`. + */ + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + none() + } + + /** + * Holds if an arbitrary number of implicit read steps of content `c` may be + * taken at `node`. + */ + predicate allowImplicitRead(Node node, ContentSet c) { none() } + + /** + * Gets the virtual dispatch branching limit when calculating field flow. + * This can be overridden to a smaller value to improve performance (a + * value of 0 disables field flow), or a larger value to get more results. + */ + int fieldFlowBranchLimit() { result = 2 } + + /** + * Gets a data flow configuration feature to add restrictions to the set of + * valid flow paths. + * + * - `FeatureHasSourceCallContext`: + * Assume that sources have some existing call context to disallow + * conflicting return-flow directly following the source. + * - `FeatureHasSinkCallContext`: + * Assume that sinks have some existing call context to disallow + * conflicting argument-to-parameter flow directly preceding the sink. + * - `FeatureEqualSourceSinkCallContext`: + * Implies both of the above and additionally ensures that the entire flow + * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. + */ + FlowFeature getAFeature() { none() } + + /** Holds if sources should be grouped in the result of `hasFlowPath`. */ + predicate sourceGrouping(Node source, string sourceGroup) { none() } + + /** Holds if sinks should be grouped in the result of `hasFlowPath`. */ + predicate sinkGrouping(Node sink, string sinkGroup) { none() } + + /** + * Holds if data may flow from `source` to `sink` for this configuration. + */ + predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) } + + /** + * Holds if data may flow from `source` to `sink` for this configuration. + * + * The corresponding paths are generated from the end-points and the graph + * included in the module `PathGraph`. + */ + predicate hasFlowPath(PathNode source, PathNode sink) { hasFlowPath(source, sink, this) } + + /** + * Holds if data may flow from some source to `sink` for this configuration. + */ + predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) } + + /** + * Holds if data may flow from some source to `sink` for this configuration. + */ + predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } + + /** + * DEPRECATED: Use `FlowExploration` instead. + * + * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` + * measured in approximate number of interprocedural steps. + */ + deprecated int explorationLimit() { none() } + + /** + * Holds if hidden nodes should be included in the data flow graph. + * + * This feature should only be used for debugging or when the data flow graph + * is not visualized (for example in a `path-problem` query). + */ + predicate includeHiddenNodes() { none() } +} + +/** + * This class exists to prevent mutual recursion between the user-overridden + * member predicates of `Configuration` and the rest of the data-flow library. + * Good performance cannot be guaranteed in the presence of such recursion, so + * it should be replaced by using more than one copy of the data flow library. + */ +abstract private class ConfigurationRecursionPrevention extends Configuration { + bindingset[this] + ConfigurationRecursionPrevention() { any() } + + override predicate hasFlow(Node source, Node sink) { + strictcount(Node n | this.isSource(n)) < 0 + or + strictcount(Node n | this.isSource(n, _)) < 0 + or + strictcount(Node n | this.isSink(n)) < 0 + or + strictcount(Node n | this.isSink(n, _)) < 0 + or + strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0 + or + strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, _, n2, _)) < 0 + or + super.hasFlow(source, sink) + } +} + +/** A bridge class to access the deprecated `isBarrierGuard`. */ +private class BarrierGuardGuardedNodeBridge extends Unit { + abstract predicate guardedNode(Node n, Configuration config); + + abstract predicate guardedNode(Node n, FlowState state, Configuration config); +} + +private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { + deprecated override predicate guardedNode(Node n, Configuration config) { + exists(BarrierGuard g | + config.isBarrierGuard(g) and + n = g.getAGuardedNode() + ) + } + + deprecated override predicate guardedNode(Node n, FlowState state, Configuration config) { + exists(BarrierGuard g | + config.isBarrierGuard(g, state) and + n = g.getAGuardedNode() + ) + } +} + +private FlowState relevantState(Configuration config) { + config.isSource(_, result) or + config.isSink(_, result) or + config.isBarrier(_, result) or + config.isAdditionalFlowStep(_, result, _, _) or + config.isAdditionalFlowStep(_, _, _, result) +} + +private newtype TConfigState = + TMkConfigState(Configuration config, FlowState state) { + state = relevantState(config) or state instanceof FlowStateEmpty + } + +private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) } + +private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) } + +private predicate singleConfiguration() { 1 = strictcount(Configuration c) } + +private module Config implements FullStateConfigSig { + class FlowState = TConfigState; + + predicate isSource(Node source, FlowState state) { + getConfig(state).isSource(source, getState(state)) + or + getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty + } + + predicate isSink(Node sink, FlowState state) { + getConfig(state).isSink(sink, getState(state)) + or + getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty + } + + predicate isBarrier(Node node) { none() } + + predicate isBarrier(Node node, FlowState state) { + getConfig(state).isBarrier(node, getState(state)) or + getConfig(state).isBarrier(node) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state)) + } + + predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) } + + predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) } + + predicate isAdditionalFlowStep(Node node1, Node node2) { + singleConfiguration() and + any(Configuration config).isAdditionalFlowStep(node1, node2) + } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and + getConfig(state2) = getConfig(state1) + or + not singleConfiguration() and + getConfig(state1).isAdditionalFlowStep(node1, node2) and + state2 = state1 + } + + predicate allowImplicitRead(Node node, ContentSet c) { + any(Configuration config).allowImplicitRead(node, c) + } + + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } + + FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } + + predicate sourceGrouping(Node source, string sourceGroup) { + any(Configuration config).sourceGrouping(source, sourceGroup) + } + + predicate sinkGrouping(Node sink, string sinkGroup) { + any(Configuration config).sinkGrouping(sink, sinkGroup) + } + + predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() } +} + +private import Impl as I +import I + +/** + * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. + * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. + */ +class PathNode instanceof I::PathNode { + /** Gets a textual representation of this element. */ + final string toString() { result = super.toString() } + + /** + * Gets a textual representation of this element, including a textual + * representation of the call context. + */ + final string toStringWithContext() { result = super.toStringWithContext() } + + /** + * 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/). + */ + final predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + + /** Gets the underlying `Node`. */ + final Node getNode() { result = super.getNode() } + + /** Gets the `FlowState` of this node. */ + final FlowState getState() { result = getState(super.getState()) } + + /** Gets the associated configuration. */ + final Configuration getConfiguration() { result = getConfig(super.getState()) } + + /** Gets a successor of this node, if any. */ + final PathNode getASuccessor() { result = super.getASuccessor() } + + /** Holds if this node is a source. */ + final predicate isSource() { super.isSource() } + + /** Holds if this node is a grouping of source nodes. */ + final predicate isSourceGroup(string group) { super.isSourceGroup(group) } + + /** Holds if this node is a grouping of sink nodes. */ + final predicate isSinkGroup(string group) { super.isSinkGroup(group) } +} + +private predicate hasFlow(Node source, Node sink, Configuration config) { + exists(PathNode source0, PathNode sink0 | + hasFlowPath(source0, sink0, config) and + source0.getNode() = source and + sink0.getNode() = sink + ) +} + +private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) { + hasFlowPath(source, sink) and source.getConfiguration() = config +} + +private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) } + +predicate flowsTo = hasFlow/3; diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll index 1b969756b09..e6bdc74cceb 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll @@ -1,16 +1,16 @@ /** - * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from - * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed - * through the `Configuration` class. This file exists in several identical - * copies, allowing queries to use multiple `Configuration` classes that depend - * on each other without introducing mutual recursion among those configurations. + * DEPRECATED: Use `Make` and `MakeWithState` instead. + * + * Provides a `Configuration` class backwards-compatible interface to the data + * flow library. */ private import DataFlowImplCommon private import DataFlowImplSpecific::Private import DataFlowImplSpecific::Public +private import DataFlowImpl import DataFlowImplCommonPublic +import FlowStateString /** * A configuration of interprocedural data flow analysis. This defines @@ -144,6 +144,10 @@ abstract class Configuration extends string { * - `FeatureEqualSourceSinkCallContext`: * Implies both of the above and additionally ensures that the entire flow * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. */ FlowFeature getAFeature() { none() } @@ -156,7 +160,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from `source` to `sink` for this configuration. */ - predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } + predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) } /** * Holds if data may flow from `source` to `sink` for this configuration. @@ -169,9 +173,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from some source to `sink` for this configuration. */ - predicate hasFlowTo(Node sink) { - sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode() - } + predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) } /** * Holds if data may flow from some source to `sink` for this configuration. @@ -179,10 +181,12 @@ abstract class Configuration extends string { predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } /** + * DEPRECATED: Use `FlowExploration` instead. + * * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` * measured in approximate number of interprocedural steps. */ - int explorationLimit() { none() } + deprecated int explorationLimit() { none() } /** * Holds if hidden nodes should be included in the data flow graph. @@ -191,49 +195,6 @@ abstract class Configuration extends string { * is not visualized (for example in a `path-problem` query). */ predicate includeHiddenNodes() { none() } - - /** - * Holds if there is a partial data flow path from `source` to `node`. The - * approximate distance between `node` and the closest source is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards sink definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sources is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - */ - final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { - partialFlow(source, node, this) and - dist = node.getSourceDistance() - } - - /** - * Holds if there is a partial data flow path from `node` to `sink`. The - * approximate distance between `node` and the closest sink is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards source definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sinks is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - * - * Note that reverse flow has slightly lower precision than the corresponding - * forward flow, as reverse flow disregards type pruning among other features. - */ - final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { - revPartialFlow(node, sink, this) and - dist = node.getSinkDistance() - } } /** @@ -263,90 +224,6 @@ abstract private class ConfigurationRecursionPrevention extends Configuration { } } -private newtype TNodeEx = - TNodeNormal(Node n) or - TNodeImplicitRead(Node n, boolean hasRead) { - any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true] - } - -private class NodeEx extends TNodeEx { - string toString() { - result = this.asNode().toString() - or - exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") - } - - Node asNode() { this = TNodeNormal(result) } - - predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } - - Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } - - pragma[nomagic] - private DataFlowCallable getEnclosingCallable0() { - nodeEnclosingCallable(this.projectToNode(), result) - } - - pragma[inline] - DataFlowCallable getEnclosingCallable() { - pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) - } - - pragma[nomagic] - private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } - - pragma[inline] - DataFlowType getDataFlowType() { - pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) - } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -private class ArgNodeEx extends NodeEx { - ArgNodeEx() { this.asNode() instanceof ArgNode } -} - -private class ParamNodeEx extends NodeEx { - ParamNodeEx() { this.asNode() instanceof ParamNode } - - predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { - this.asNode().(ParamNode).isParameterOf(c, pos) - } - - ParameterPosition getPosition() { this.isParameterOf(_, result) } -} - -private class RetNodeEx extends NodeEx { - RetNodeEx() { this.asNode() instanceof ReturnNodeExt } - - ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } - - ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } -} - -private predicate inBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierIn(n) - | - config.isSource(n) or config.isSource(n, _) - ) -} - -private predicate outBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierOut(n) - | - config.isSink(n) or config.isSink(n, _) - ) -} - /** A bridge class to access the deprecated `isBarrierGuard`. */ private class BarrierGuardGuardedNodeBridge extends Unit { abstract predicate guardedNode(Node n, Configuration config); @@ -370,3019 +247,94 @@ private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { } } -pragma[nomagic] -private predicate fullBarrier(NodeEx node, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n) +private FlowState relevantState(Configuration config) { + config.isSource(_, result) or + config.isSink(_, result) or + config.isBarrier(_, result) or + config.isAdditionalFlowStep(_, result, _, _) or + config.isAdditionalFlowStep(_, _, _, result) +} + +private newtype TConfigState = + TMkConfigState(Configuration config, FlowState state) { + state = relevantState(config) or state instanceof FlowStateEmpty + } + +private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) } + +private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) } + +private predicate singleConfiguration() { 1 = strictcount(Configuration c) } + +private module Config implements FullStateConfigSig { + class FlowState = TConfigState; + + predicate isSource(Node source, FlowState state) { + getConfig(state).isSource(source, getState(state)) or - config.isBarrierIn(n) and - not config.isSource(n) and - not config.isSource(n, _) + getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty + } + + predicate isSink(Node sink, FlowState state) { + getConfig(state).isSink(sink, getState(state)) or - config.isBarrierOut(n) and - not config.isSink(n) and - not config.isSink(n, _) + getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty + } + + predicate isBarrier(Node node) { none() } + + predicate isBarrier(Node node, FlowState state) { + getConfig(state).isBarrier(node, getState(state)) or + getConfig(state).isBarrier(node) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state)) + } + + predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) } + + predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) } + + predicate isAdditionalFlowStep(Node node1, Node node2) { + singleConfiguration() and + any(Configuration config).isAdditionalFlowStep(node1, node2) + } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and + getConfig(state2) = getConfig(state1) or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config) - ) -} - -pragma[nomagic] -private predicate stateBarrier(NodeEx node, FlowState state, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n, state) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config) - ) -} - -pragma[nomagic] -private predicate sourceNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSource(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSource(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -pragma[nomagic] -private predicate sinkNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSink(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSink(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -/** Provides the relevant barriers for a step from `node1` to `node2`. */ -pragma[inline] -private predicate stepFilter(NodeEx node1, NodeEx node2, Configuration config) { - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) -} - -/** - * Holds if data can flow in one local step from `node1` to `node2`. - */ -private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.asNode() = n and - node2.isImplicitReadNode(n, false) and - not fullBarrier(node1, config) - ) -} - -/** - * Holds if the additional step from `node1` to `node2` does not jump between callables. - */ -private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.isImplicitReadNode(n, true) and - node2.asNode() = n and - not fullBarrier(node2, config) - ) -} - -private predicate additionalLocalStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) - ) -} - -/** - * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. - */ -private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if the additional step from `node1` to `node2` jumps between callables. - */ -private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate additionalJumpStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -pragma[nomagic] -private predicate readSet(NodeEx node1, ContentSet c, NodeEx node2, Configuration config) { - readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and - stepFilter(node1, node2, config) - or - exists(Node n | - node2.isImplicitReadNode(n, true) and - node1.isImplicitReadNode(n, _) and - config.allowImplicitRead(n, c) - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(ContentSet cs | - readSet(node1, cs, node2, config) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate clearsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - clearsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate expectsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - expectsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -pragma[nomagic] -private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } - -pragma[nomagic] -private predicate hasReadStep(Content c, Configuration config) { read(_, c, _, config) } - -pragma[nomagic] -private predicate store( - NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config -) { - store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), - contentType) and - hasReadStep(tc.getContent(), config) and - stepFilter(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { - viableReturnPosOut(call, pos, out.asNode()) -} - -pragma[nomagic] -private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { - viableParamArg(call, p.asNode(), arg.asNode()) -} - -/** - * Holds if field flow should be used for the given configuration. - */ -private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } - -private predicate hasSourceCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSourceCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate hasSinkCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSinkCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if flow from `p` to a return node of kind `kind` is allowed. - * - * We don't expect a parameter to return stored in itself, unless - * explicitly allowed - */ -bindingset[p, kind] -private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { - exists(ParameterPosition pos | p.isParameterOf(_, pos) | - not kind.(ParamUpdateReturnKind).getPosition() = pos - or - allowParameterReturnInSelfCached(p.asNode()) - ) -} - -private module Stage1 implements StageSig { - class Ap extends int { - // workaround for bad functionality-induced joins (happens when using `Unit`) - pragma[nomagic] - Ap() { this in [0 .. 1] and this < 1 } - } - - private class Cc = boolean; - - /* Begin: Stage 1 logic. */ - /** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `cc` records whether the node is reached through an - * argument in a call. - */ - private predicate fwdFlow(NodeEx node, Cc cc, Configuration config) { - sourceNode(node, _, config) and - if hasSourceCallCtx(config) then cc = true else cc = false - or - exists(NodeEx mid | fwdFlow(mid, cc, config) | - localFlowStep(mid, node, config) or - additionalLocalFlowStep(mid, node, config) or - additionalLocalStateStep(mid, _, node, _, config) - ) - or - exists(NodeEx mid | fwdFlow(mid, _, config) and cc = false | - jumpStep(mid, node, config) or - additionalJumpStep(mid, node, config) or - additionalJumpStateStep(mid, _, node, _, config) - ) - or - // store - exists(NodeEx mid | - useFieldFlow(config) and - fwdFlow(mid, cc, config) and - store(mid, _, node, _, config) - ) - or - // read - exists(ContentSet c | - fwdFlowReadSet(c, node, cc, config) and - fwdFlowConsCandSet(c, _, config) - ) - or - // flow into a callable - fwdFlowIn(_, _, _, node, config) and - cc = true - or - // flow out of a callable - fwdFlowOut(_, node, false, config) and - cc = false - or - // flow through a callable - exists(DataFlowCall call | - fwdFlowOutFromArg(call, node, config) and - fwdFlowIsEntered(call, cc, config) - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowIn( - DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p, Configuration config - ) { - // call context cannot help reduce virtual dispatch - fwdFlow(arg, cc, config) and - viableParamArgEx(call, p, arg) and - not fullBarrier(p, config) and - ( - cc = false - or - cc = true and - not reducedViableImplInCallContext(call, _, _) - ) - or - // call context may help reduce virtual dispatch - exists(DataFlowCallable target | - fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target, config) and - target = viableImplInSomeFwdFlowCallContextExt(call, config) and - cc = true - ) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - fwdFlowIn(call, _, cc, _, config) - } - - pragma[nomagic] - private predicate fwdFlowInReducedViableImplInSomeCallContext( - DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target, Configuration config - ) { - fwdFlow(arg, true, config) and - viableParamArgEx(call, p, arg) and - reducedViableImplInCallContext(call, _, _) and - target = p.getEnclosingCallable() and - not fullBarrier(p, config) - } - - /** - * Gets a viable dispatch target of `call` in the context `ctx`. This is - * restricted to those `call`s for which a context might make a difference, - * and to `ctx`s that are reachable in `fwdFlow`. - */ - pragma[nomagic] - private DataFlowCallable viableImplInSomeFwdFlowCallContextExt( - DataFlowCall call, Configuration config - ) { - exists(DataFlowCall ctx | - fwdFlowIsEntered(ctx, _, config) and - result = viableImplInCallContextExt(call, ctx) - ) - } - - private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) } - - pragma[nomagic] - private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc, Configuration config) { - exists(NodeEx mid | - fwdFlow(mid, cc, config) and - readSet(mid, c, node, config) - ) - } - - /** - * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - fwdFlow(mid, _, config) and - store(mid, tc, node, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `cs` may be interpreted in a read as the target of some store - * into `c`, in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCandSet(ContentSet cs, Content c, Configuration config) { - fwdFlowConsCand(c, config) and - c = cs.getAReadContent() - } - - pragma[nomagic] - private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { - exists(RetNodeEx ret | - fwdFlow(ret, cc, config) and - ret.getReturnPosition() = pos - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) { - exists(ReturnPosition pos | - fwdFlowReturnPosition(pos, cc, config) and - viableReturnPosOutEx(call, pos, out) and - not fullBarrier(out, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) { - fwdFlowOut(call, out, true, config) - } - - private predicate stateStepFwd(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1 | - additionalLocalStateStep(node1, state1, _, state2, config) or - additionalJumpStateStep(node1, state1, _, state2, config) - | - fwdFlow(node1, config) - ) - } - - private predicate fwdFlowState(FlowState state, Configuration config) { - sourceNode(_, state, config) - or - exists(FlowState state0 | - fwdFlowState(state0, config) and - stateStepFwd(state0, state, config) - ) - } - - /** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink. - */ - pragma[nomagic] - private predicate revFlow(NodeEx node, boolean toReturn, Configuration config) { - revFlow0(node, toReturn, config) and - fwdFlow(node, config) - } - - pragma[nomagic] - private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) { - exists(FlowState state | - fwdFlow(node, pragma[only_bind_into](config)) and - sinkNode(node, state, config) and - fwdFlowState(state, pragma[only_bind_into](config)) and - if hasSinkCallCtx(config) then toReturn = true else toReturn = false - ) - or - exists(NodeEx mid | revFlow(mid, toReturn, config) | - localFlowStep(node, mid, config) or - additionalLocalFlowStep(node, mid, config) or - additionalLocalStateStep(node, _, mid, _, config) - ) - or - exists(NodeEx mid | revFlow(mid, _, config) and toReturn = false | - jumpStep(node, mid, config) or - additionalJumpStep(node, mid, config) or - additionalJumpStateStep(node, _, mid, _, config) - ) - or - // store - exists(Content c | - revFlowStore(c, node, toReturn, config) and - revFlowConsCand(c, config) - ) - or - // read - exists(NodeEx mid, ContentSet c | - readSet(node, c, mid, config) and - fwdFlowConsCandSet(c, _, pragma[only_bind_into](config)) and - revFlow(mid, toReturn, pragma[only_bind_into](config)) - ) - or - // flow into a callable - revFlowIn(_, node, false, config) and - toReturn = false - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(pos, config) and - node.(RetNodeEx).getReturnPosition() = pos and - toReturn = true - ) - or - // flow through a callable - exists(DataFlowCall call | - revFlowInToReturn(call, node, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - /** - * Holds if `c` is the target of a read in the flow covered by `revFlow`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, ContentSet cs | - fwdFlow(node, pragma[only_bind_into](config)) and - readSet(node, cs, mid, config) and - fwdFlowConsCandSet(cs, c, pragma[only_bind_into](config)) and - revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) { - exists(NodeEx mid, TypedContent tc | - revFlow(mid, toReturn, pragma[only_bind_into](config)) and - fwdFlowConsCand(c, pragma[only_bind_into](config)) and - store(node, tc, mid, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `c` is the target of both a read and a store in the flow covered - * by `revFlow`. - */ - pragma[nomagic] - additional predicate revFlowIsReadAndStored(Content c, Configuration conf) { - revFlowConsCand(c, conf) and - revFlowStore(c, _, _, conf) - } - - pragma[nomagic] - additional predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config - ) { - fwdFlowReturnPosition(pos, _, config) and - viableReturnPosOutEx(call, pos, out) - } - - pragma[nomagic] - private predicate revFlowOut(ReturnPosition pos, Configuration config) { - exists(NodeEx out | - revFlow(out, _, config) and - viableReturnPosOutNodeCandFwd1(_, pos, out, config) - ) - } - - pragma[nomagic] - additional predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config - ) { - fwdFlowIn(call, arg, _, p, config) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate revFlowIn( - DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config - ) { - exists(ParamNodeEx p | - revFlow(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) { - revFlowIn(call, arg, true, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(NodeEx out | - revFlow(out, toReturn, config) and - fwdFlowOutFromArg(call, out, config) - ) - } - - private predicate stateStepRev(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - additionalLocalStateStep(node1, state1, node2, state2, config) or - additionalJumpStateStep(node1, state1, node2, state2, config) - | - revFlow(node1, _, pragma[only_bind_into](config)) and - revFlow(node2, _, pragma[only_bind_into](config)) and - fwdFlowState(state1, pragma[only_bind_into](config)) and - fwdFlowState(state2, pragma[only_bind_into](config)) - ) - } - - additional predicate revFlowState(FlowState state, Configuration config) { - exists(NodeEx node | - sinkNode(node, state, config) and - revFlow(node, _, pragma[only_bind_into](config)) and - fwdFlowState(state, pragma[only_bind_into](config)) - ) - or - exists(FlowState state0 | - revFlowState(state0, config) and - stateStepRev(state, state0, config) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Content c | - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - revFlow(node2, pragma[only_bind_into](config)) and - store(node1, tc, node2, contentType, config) and - c = tc.getContent() and - exists(ap1) - ) - } - - pragma[nomagic] - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) { - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - read(n1, c, n2, pragma[only_bind_into](config)) and - revFlow(n2, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, config) and - exists(ap) - } - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, _, pragma[only_bind_into](config)) and - exists(state) and - exists(ap) - } - - private predicate throughFlowNodeCand(NodeEx node, Configuration config) { - revFlow(node, true, config) and - fwdFlow(node, true, config) and - not inBarrier(node, config) and - not outBarrier(node, config) - } - - /** Holds if flow may return from `callable`. */ - pragma[nomagic] - private predicate returnFlowCallableNodeCand( - DataFlowCallable callable, ReturnKindExt kind, Configuration config - ) { - exists(RetNodeEx ret | - throughFlowNodeCand(ret, config) and - callable = ret.getEnclosingCallable() and - kind = ret.getKind() - ) - } - - /** - * Holds if flow may enter through `p` and reach a return node making `p` a - * candidate for the origin of a summary. - */ - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(DataFlowCallable c, ReturnKindExt kind | - throughFlowNodeCand(p, config) and - returnFlowCallableNodeCand(c, kind, config) and - p.getEnclosingCallable() = c and - exists(ap) and - parameterFlowThroughAllowed(p, kind) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - throughFlowNodeCand(ret, config) and - kind = ret.getKind() and - exists(argAp) and - exists(ap) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, boolean toReturn | - revFlow(arg, toReturn, config) and - revFlowInToReturn(call, arg, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, config)) and - fields = count(Content f0 | fwdFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | fwdFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, config)) and - fields = count(Content f0 | revFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | revFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | revFlow(n, b, config)) - } - /* End: Stage 1 logic. */ -} - -pragma[noinline] -private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - localFlowStep(node1, node2, config) -} - -pragma[noinline] -private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - additionalLocalFlowStep(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCand1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config -) { - Stage1::revFlow(out, config) and - Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, Configuration config -) { - exists(ReturnPosition pos | - viableReturnPosOutNodeCand1(call, pos, out, config) and - pos = ret.getReturnPosition() and - kind = pos.getKind() and - Stage1::revFlow(ret, config) and - not outBarrier(ret, config) and - not inBarrier(out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCand1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config -) { - Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and - Stage1::revFlow(arg, config) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config -) { - viableParamArgNodeCand1(call, p, arg, config) and - Stage1::revFlow(p, config) and - not outBarrier(arg, config) and - not inBarrier(p, config) -} - -/** - * Gets the amount of forward branching on the origin of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int branch(NodeEx n1, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n1, _, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) - ) -} - -/** - * Gets the amount of backward branching on the target of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int join(NodeEx n2, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n, _, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) - ) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. The - * `allowsFieldFlow` flag indicates whether the branching is within the limit - * specified by the configuration. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, ret, kind, out, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(ret, pragma[only_bind_into](config)) and - j = join(out, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. The `allowsFieldFlow` flag indicates whether - * the branching is within the limit specified by the configuration. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config -) { - flowIntoCallNodeCand1(call, arg, p, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(arg, pragma[only_bind_into](config)) and - j = join(p, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -private signature module StageSig { - class Ap; - - predicate revFlow(NodeEx node, Configuration config); - - predicate revFlowAp(NodeEx node, Ap ap, Configuration config); - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config); - - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config); - - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config); - - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ); - - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ); - - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config); -} - -private module MkStage { - class ApApprox = PrevStage::Ap; - - signature module StageParam { - class Ap; - - class ApNil extends Ap; - - bindingset[result, ap] - ApApprox getApprox(Ap ap); - - ApNil getApNil(NodeEx node); - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail); - - /** - * An approximation of `Content` that corresponds to the precision level of - * `Ap`, such that the mappings from both `Ap` and `Content` to this type - * are functional. - */ - class ApHeadContent; - - ApHeadContent getHeadContent(Ap ap); - - ApHeadContent projectToHeadContent(Content c); - - class ApOption; - - ApOption apNone(); - - ApOption apSome(Ap ap); - - class Cc; - - class CcCall extends Cc; - - // TODO: member predicate on CcCall - predicate matchesCall(CcCall cc, DataFlowCall call); - - class CcNoCall extends Cc; - - Cc ccNone(); - - CcCall ccSomeCall(); - - class LocalCc; - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc); - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ); - - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config - ); - - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config - ); - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config); - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType); - } - - module Stage implements StageSig { - import Param - - /* Begin: Stage logic. */ - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - private predicate revFlowApAlias(NodeEx node, ApApprox apa, Configuration config) { - PrevStage::revFlowAp(node, apa, config) - } - - pragma[nomagic] - private predicate flowIntoCallApa( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa, - Configuration config - ) { - flowIntoCall(call, arg, p, allowsFieldFlow, config) and - PrevStage::revFlowAp(p, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(arg, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowOutOfCallApa( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - ApApprox apa, Configuration config - ) { - flowOutOfCall(call, ret, kind, out, allowsFieldFlow, config) and - PrevStage::revFlowAp(out, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(ret, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowThroughOutOfCall( - DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, - ApApprox argApa, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa, pragma[only_bind_into](config)) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) and - matchesCall(ccc, call) - ) - } - - /** - * Holds if `node` is reachable with access path `ap` from a source in the - * configuration `config`. - * - * The call context `cc` records whether the node is reached through an - * argument in a call, and if so, `summaryCtx` and `argAp` record the - * corresponding parameter position and access path of that argument, respectively. - */ - pragma[nomagic] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa, config) and - PrevStage::revFlow(node, state, apa, config) and - filter(node, state, ap, config) - } - - pragma[inline] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, cc, summaryCtx, argAp, ap, _, config) - } - - pragma[nomagic] - private predicate fwdFlow0( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - sourceNode(node, state, config) and - (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and - argAp = apNone() and - summaryCtx = TParamNodeNone() and - ap = getApNil(node) and - apa = getApprox(ap) - or - exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | - fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0, config) and - localCc = getLocalCc(mid, cc) - | - localStep(mid, state0, node, state, true, _, config, localCc) and - ap = ap0 and - apa = apa0 - or - localStep(mid, state0, node, state, false, ap, config, localCc) and - ap0 instanceof ApNil and - apa = getApprox(ap) - ) - or - exists(NodeEx mid | - fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa, pragma[only_bind_into](config)) and - jumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(mid, state, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(mid, state0, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStateStep(mid, state0, node, state, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - // store - exists(TypedContent tc, Ap ap0 | - fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp, config) and - ap = apCons(tc, ap0) and - apa = getApprox(ap) - ) - or - // read - exists(Ap ap0, Content c | - fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp, config) and - fwdFlowConsCand(ap0, c, ap, config) and - apa = getApprox(ap) - ) - or - // flow into a callable - fwdFlowIn(_, node, state, _, cc, _, _, ap, apa, config) and - if PrevStage::parameterMayFlowThrough(node, apa, config) - then ( - summaryCtx = TParamNodeSome(node.asNode()) and - argAp = apSome(ap) - ) else ( - summaryCtx = TParamNodeNone() and argAp = apNone() - ) - or - // flow out of a callable - exists( - DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, - DataFlowCallable inner - | - fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa, config) and - flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa, config) and - inner = ret.getEnclosingCallable() and - cc = getCallContextReturn(inner, call, innercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - or - // flow through a callable - exists( - DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, ApApprox innerArgApa - | - fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowStore( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(DataFlowType contentType, ApApprox apa1 | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1, config) and - PrevStage::storeStepCand(node1, apa1, tc, node2, contentType, config) and - typecheckStore(ap1, contentType) - ) - } - - /** - * Holds if forward flow with access path `tail` reaches a store of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(TypedContent tc | - fwdFlowStore(_, tail, tc, _, _, _, _, _, config) and - tc.getContent() = c and - cons = apCons(tc, tail) - ) - } - - pragma[nomagic] - private predicate readStepCand( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - PrevStage::readStepCand(node1, c, node2, config) and - apc = projectToHeadContent(c) - } - - bindingset[node1, apc] - pragma[inline_late] - private predicate readStepCand0( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - readStepCand(node1, apc, c, node2, config) - } - - pragma[nomagic] - private predicate fwdFlowRead( - Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(ApHeadContent apc | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap, config) and - apc = getHeadContent(ap) and - readStepCand0(node1, apc, c, node2, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowIn( - DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, - ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa, Configuration config - ) { - exists(ArgNodeEx arg, boolean allowsFieldFlow | - fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa, config) and - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowRetFromArg( - RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, ApApprox argApa, - Ap ap, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - fwdFlow(pragma[only_bind_into](ret), state, ccc, - TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), - pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa), - pragma[only_bind_into](config)) and - kind = ret.getKind() and - parameterFlowThroughAllowed(summaryCtx, kind) and - argApa = getApprox(argAp) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) - ) - } - - pragma[inline] - private predicate fwdFlowThrough0( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, - Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa, config) and - fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp, config) - } - - pragma[nomagic] - private predicate fwdFlowThrough( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa, - config) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` - * and data might flow through the target callable and back out at `call`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered( - DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, - ParamNodeEx p, Ap ap, Configuration config - ) { - exists(ApApprox apa | - fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(p, apa, config) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate storeStepFwd( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config - ) { - fwdFlowStore(node1, ap1, tc, node2, _, _, _, _, config) and - ap2 = apCons(tc, ap1) and - fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _, config) - } - - private predicate readStepFwd( - NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config - ) { - fwdFlowRead(ap1, c, n1, n2, _, _, _, _, config) and - fwdFlowConsCand(ap1, c, ap2, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough0( - DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, - ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, - innerArgApa, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough( - RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, - Ap ap, Configuration config - ) { - exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | - returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa, config) and - pos = ret.getReturnPosition() and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowThroughIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap, - Configuration config - ) { - exists(ApApprox argApa | - flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), - allowsFieldFlow, argApa, pragma[only_bind_into](config)) and - fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa, - pragma[only_bind_into](config)) and - returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap, - pragma[only_bind_into](config)) and - if allowsFieldFlow = false then argAp instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowIntoCallAp( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap, - Configuration config - ) { - exists(ApApprox apa | - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - fwdFlow(arg, _, _, _, _, ap, apa, config) - ) - } - - pragma[nomagic] - private predicate flowOutOfCallAp( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, - Ap ap, Configuration config - ) { - exists(ApApprox apa | - flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa, config) and - fwdFlow(ret, _, _, _, _, ap, apa, config) and - pos = ret.getReturnPosition() - ) - } - - /** - * Holds if `node` with access path `ap` is part of a path from a source to a - * sink in the configuration `config`. - * - * The parameter `returnCtx` records whether (and how) the node must be returned - * from the enclosing callable in order to reach a sink, and if so, `returnAp` - * records the access path of the returned value. - */ - pragma[nomagic] - additional predicate revFlow( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - revFlow0(node, state, returnCtx, returnAp, ap, config) and - fwdFlow(node, state, _, _, _, ap, config) - } - - pragma[nomagic] - private predicate revFlow0( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, _, _, _, ap, config) and - sinkNode(node, state, config) and - ( - if hasSinkCallCtx(config) - then returnCtx = TReturnCtxNoFlowThrough() - else returnCtx = TReturnCtxNone() - ) and - returnAp = apNone() and - ap instanceof ApNil - or - exists(NodeEx mid, FlowState state0 | - localStep(node, state, mid, state0, true, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, ap, config) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap, pragma[only_bind_into](config)) and - localStep(node, pragma[only_bind_into](state), mid, state0, false, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, nil, pragma[only_bind_into](config)) and - ap instanceof ApNil - ) - or - exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, state, _, _, ap, config) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStep(node, mid, config) and - revFlow(pragma[only_bind_into](mid), state, _, _, nil, pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStateStep(node, state, mid, state0, config) and - revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil, - pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - // store - exists(Ap ap0, Content c | - revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp, config) and - revFlowConsCand(ap0, c, ap, config) - ) - or - // read - exists(NodeEx mid, Ap ap0 | - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - readStepFwd(node, ap, _, mid, ap0, config) - ) - or - // flow into a callable - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, state, TReturnCtxNone(), returnAp, ap, config) and - flowIntoCallAp(_, node, p, allowsFieldFlow, ap, config) and - (if allowsFieldFlow = false then ap instanceof ApNil else any()) and - returnCtx = TReturnCtxNone() - ) - or - // flow through a callable - exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, node, p, _, ap, innerReturnAp, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(_, node, pos, state, _, _, ap, config) and - if returnFlowsThrough(node, pos, state, _, _, _, ap, config) - then ( - returnCtx = TReturnCtxMaybeFlowThrough(pos) and - returnAp = apSome(ap) - ) else ( - returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() - ) - ) - } - - pragma[nomagic] - private predicate revFlowStore( - Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, - ReturnCtx returnCtx, ApOption returnAp, Configuration config - ) { - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - storeStepFwd(node, ap, tc, mid, ap0, config) and - tc.getContent() = c - } - - /** - * Holds if reverse flow with access path `tail` reaches a read of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(NodeEx mid, Ap tail0 | - revFlow(mid, _, _, _, tail, config) and - tail = pragma[only_bind_into](tail0) and - readStepFwd(_, cons, c, mid, tail0, config) - ) - } - - pragma[nomagic] - private predicate revFlowOut( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, - ApOption returnAp, Ap ap, Configuration config - ) { - exists(NodeEx out, boolean allowsFieldFlow | - revFlow(out, state, returnCtx, returnAp, ap, config) and - flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate revFlowParamToReturn( - ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap, Configuration config - ) { - revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), - pragma[only_bind_into](ap), pragma[only_bind_into](config)) and - parameterFlowThroughAllowed(p, pos.getKind()) and - PrevStage::parameterMayFlowThrough(p, getApprox(ap), config) - } - - pragma[nomagic] - private predicate revFlowThrough( - DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, - ApOption returnAp, Ap ap, Ap innerReturnAp, Configuration config - ) { - revFlowParamToReturn(p, state, pos, innerReturnAp, ap, config) and - revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned( - DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap, - Configuration config - ) { - exists(RetNodeEx ret, FlowState state, CcCall ccc | - revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap, config) and - returnFlowsThrough(ret, pos, state, ccc, _, _, ap, config) and - matchesCall(ccc, call) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Ap ap2, Content c | - PrevStage::storeStepCand(node1, _, tc, node2, contentType, config) and - revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _, config) and - revFlowConsCand(ap2, c, ap1, config) - ) - } - - predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(Ap ap1, Ap ap2 | - revFlow(node2, _, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and - readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _, - pragma[only_bind_into](config)) - ) - } - - additional predicate revFlow(NodeEx node, FlowState state, Configuration config) { - revFlow(node, state, _, _, _, config) - } - - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, _, _, ap, config) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, _, _, _, ap, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, Configuration config) { - revFlow(node, _, _, _, _, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, ap, config) - } - - private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepFwd(_, ap, tc, _, _, config) - } - - private predicate revConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepCand(_, ap, tc, _, _, config) - } - - private predicate validAp(Ap ap, Configuration config) { - revFlow(_, _, _, _, ap, config) and ap instanceof ApNil - or - exists(TypedContent head, Ap tail | - consCand(head, tail, config) and - ap = apCons(head, tail) - ) - } - - additional predicate consCand(TypedContent tc, Ap ap, Configuration config) { - revConsCand(tc, ap, config) and - validAp(ap, config) - } - - pragma[nomagic] - private predicate parameterFlowsThroughRev( - ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp, Configuration config - ) { - revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap, config) and - parameterFlowThroughAllowed(p, pos.getKind()) - } - - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(ReturnPosition pos | - returnFlowsThrough(_, pos, _, _, p, ap, _, config) and - parameterFlowsThroughRev(p, ap, pos, _, config) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - exists(ParamNodeEx p, ReturnPosition pos | - returnFlowsThrough(ret, pos, _, _, p, argAp, ap, config) and - parameterFlowsThroughRev(p, argAp, pos, ap, config) and - kind = pos.getKind() - ) - } - - pragma[nomagic] - private predicate revFlowThroughArg( - DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, - Ap ap, Configuration config - ) { - exists(ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp, config) - ) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | - revFlow(arg, state, returnCtx, returnAp, ap, config) and - revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, config)) and - fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and - states = count(FlowState state | fwdFlow(_, state, _, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | - fwdFlow(n, state, cc, summaryCtx, argAp, ap, config) - ) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, _, _, _, config)) and - fields = count(TypedContent f0 | consCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and - states = count(FlowState state | revFlow(_, state, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | - revFlow(n, state, returnCtx, retAp, ap, config) - ) - } - /* End: Stage logic. */ - } -} - -private module BooleanCallContext { - class Cc extends boolean { - Cc() { this in [true, false] } - } - - class CcCall extends Cc { - CcCall() { this = true } - } - - /** Holds if the call context may be `call`. */ - predicate matchesCall(CcCall cc, DataFlowCall call) { any() } - - class CcNoCall extends Cc { - CcNoCall() { this = false } - } - - Cc ccNone() { result = false } - - CcCall ccSomeCall() { result = true } - - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } -} - -private module Level1CallContext { - class Cc = CallContext; - - class CcCall = CallContextCall; - - pragma[inline] - predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } - - class CcNoCall = CallContextNoCall; - - Cc ccNone() { result instanceof CallContextAny } - - CcCall ccSomeCall() { result instanceof CallContextSomeCall } - - module NoLocalCallContext { - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSiteDispatch(call, c) - then result = TSpecificCall(call) - else result = TSomeCall() - } - } - - module LocalCallContext { - class LocalCc = LocalCallContext; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { - result = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - node.getEnclosingCallable()) - } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() - } - } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { - checkCallContextReturn(innercc, c, call) and - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() - } -} - -private module Stage2Param implements MkStage::StageParam { - private module PrevStage = Stage1; - - class Ap extends boolean { - Ap() { this in [true, false] } - } - - class ApNil extends Ap { - ApNil() { this = false } - } - - bindingset[result, ap] - PrevStage::Ap getApprox(Ap ap) { any() } - - ApNil getApNil(NodeEx node) { Stage1::revFlow(node, _) and exists(result) } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } - - class ApHeadContent = Unit; - - pragma[inline] - ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } - - ApHeadContent projectToHeadContent(Content c) { any() } - - class ApOption = BooleanOption; - - ApOption apNone() { result = TBooleanNone() } - - ApOption apSome(Ap ap) { result = TBooleanSome(ap) } - - import Level1CallContext - import NoLocalCallContext - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - ( - preservesValue = true and - localFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalStateStep(node1, state1, node2, state2, config) - ) and - exists(ap) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand1/6; - - predicate flowIntoCall = flowIntoCallNodeCand1/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - expectsContentEx(node, c) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - PrevStage::revFlowState(state, pragma[only_bind_into](config)) and - exists(ap) and - not stateBarrier(node, state, config) and - ( - notExpectsContent(node) - or - ap = true and - expectsContentCand(node, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage2 implements StageSig { - import MkStage::Stage -} - -pragma[nomagic] -private predicate flowOutOfCallNodeCand2( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -pragma[nomagic] -private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -private module LocalFlowBigStep { - /** - * A node where some checking is required, and hence the big-step relation - * is not allowed to step over. - */ - private class FlowCheckNode extends NodeEx { - FlowCheckNode() { - castNode(this.asNode()) or - clearsContentCached(this.asNode(), _) or - expectsContentCached(this.asNode(), _) - } - } - - /** - * Holds if `node` can be the first node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowEntry(NodeEx node, FlowState state, Configuration config) { - Stage2::revFlow(node, state, config) and - ( - sourceNode(node, state, config) - or - jumpStep(_, node, config) - or - additionalJumpStep(_, node, config) - or - additionalJumpStateStep(_, _, node, state, config) - or - node instanceof ParamNodeEx - or - node.asNode() instanceof OutNodeExt - or - Stage2::storeStepCand(_, _, _, node, _, config) - or - Stage2::readStepCand(_, _, node, config) - or - node instanceof FlowCheckNode - or - exists(FlowState s | - additionalLocalStateStep(_, s, node, state, config) and - s != state - ) - ) - } - - /** - * Holds if `node` can be the last node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowExit(NodeEx node, FlowState state, Configuration config) { - exists(NodeEx next | Stage2::revFlow(next, state, config) | - jumpStep(node, next, config) or - additionalJumpStep(node, next, config) or - flowIntoCallNodeCand2(_, node, next, _, config) or - flowOutOfCallNodeCand2(_, node, _, next, _, config) or - Stage2::storeStepCand(node, _, _, next, _, config) or - Stage2::readStepCand(node, _, next, config) - ) - or - exists(NodeEx next, FlowState s | Stage2::revFlow(next, s, config) | - additionalJumpStateStep(node, state, next, s, config) - or - additionalLocalStateStep(node, state, next, s, config) and - s != state - ) - or - Stage2::revFlow(node, state, config) and - node instanceof FlowCheckNode - or - sinkNode(node, state, config) - } - - pragma[noinline] - private predicate additionalLocalFlowStepNodeCand2( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, Configuration config - ) { - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 and - Stage2::revFlow(node1, pragma[only_bind_into](state1), false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false, - pragma[only_bind_into](config)) - or - additionalLocalStateStep(node1, state1, node2, state2, config) and - Stage2::revFlow(node1, state1, false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, state2, false, pragma[only_bind_into](config)) - } - - /** - * Holds if the local path from `node1` to `node2` is a prefix of a maximal - * subsequence of local flow steps in a dataflow path. - * - * This is the transitive closure of `[additional]localFlowStep` beginning - * at `localFlowEntry`. - */ - pragma[nomagic] - private predicate localFlowStepPlus( - NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, - Configuration config, LocalCallContext cc - ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and - ( - localFlowEntry(node1, pragma[only_bind_into](state), pragma[only_bind_into](config)) and - ( - localFlowStepNodeCand1(node1, node2, config) and - preservesValue = true and - t = node1.getDataFlowType() and // irrelevant dummy value - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - or - additionalLocalFlowStepNodeCand2(node1, state, node2, state, config) and - preservesValue = false and - t = node2.getDataFlowType() - ) and - node1 != node2 and - cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) - or - exists(NodeEx mid | - localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, - pragma[only_bind_into](config), cc) and - localFlowStepNodeCand1(mid, node2, config) and - not mid instanceof FlowCheckNode and - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - ) - or - exists(NodeEx mid | - localFlowStepPlus(node1, state, mid, _, _, pragma[only_bind_into](config), cc) and - additionalLocalFlowStepNodeCand2(mid, state, node2, state, config) and - not mid instanceof FlowCheckNode and - preservesValue = false and - t = node2.getDataFlowType() - ) - ) - } - - /** - * Holds if `node1` can step to `node2` in one or more local steps and this - * path can occur as a maximal subsequence of local steps in a dataflow path. - */ - pragma[nomagic] - predicate localFlowBigStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - DataFlowType t, Configuration config, LocalCallContext callContext - ) { - localFlowStepPlus(node1, state1, node2, preservesValue, t, config, callContext) and - localFlowExit(node2, state1, config) and - state1 = state2 - or - additionalLocalFlowStepNodeCand2(node1, state1, node2, state2, config) and - state1 != state2 and - preservesValue = false and - t = node2.getDataFlowType() and - callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) - } -} - -private import LocalFlowBigStep - -private module Stage3Param implements MkStage::StageParam { - private module PrevStage = Stage2; - - class Ap = ApproxAccessPathFront; - - class ApNil = ApproxAccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } - - class ApHeadContent = ContentApprox; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - predicate projectToHeadContent = getContentApprox/1; - - class ApOption = ApproxAccessPathFrontOption; - - ApOption apNone() { result = TApproxAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } - - import BooleanCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApproxAccessPathFrontNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand2/6; - - predicate flowIntoCall = flowIntoCallNodeCand2/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getAHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage3 implements StageSig { - import MkStage::Stage -} - -private module Stage4Param implements MkStage::StageParam { - private module PrevStage = Stage3; - - class Ap = AccessPathFront; - - class ApNil = AccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathFrontOption; - - ApOption apNone() { result = TAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } - - import BooleanCallContext - - pragma[nomagic] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) and - exists(lcc) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clearSet(NodeEx node, ContentSet c, Configuration config) { - PrevStage::revFlow(node, config) and - clearsContentCached(node.asNode(), c) - } - - pragma[nomagic] - private predicate clearContent(NodeEx node, Content c, Configuration config) { - exists(ContentSet cs | - PrevStage::readStepCand(_, pragma[only_bind_into](c), _, pragma[only_bind_into](config)) and - c = cs.getAReadContent() and - clearSet(node, cs, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clear(NodeEx node, Ap ap, Configuration config) { - clearContent(node, ap.getHead().getContent(), config) - } - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - not clear(node, ap, config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage4 implements StageSig { - import MkStage::Stage -} - -/** - * Holds if `argApf` is recorded as the summary context for flow reaching `node` - * and remains relevant for the following pruning stage. - */ -private predicate flowCandSummaryCtx( - NodeEx node, FlowState state, AccessPathFront argApf, Configuration config -) { - exists(AccessPathFront apf | - Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf, config) and - Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf, - config) - ) -} - -/** - * Holds if a length 2 access path approximation with the head `tc` is expected - * to be expensive. - */ -private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { - exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf, config)) and - nodes = - strictcount(NodeEx n, FlowState state | - Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - or - flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - ) and - accessPathApproxCostLimits(apLimit, tupleLimit) and - apLimit < tails and - tupleLimit < (tails - 1) * nodes and - not tc.forceHighPrecision() - ) -} - -private newtype TAccessPathApprox = - TNil(DataFlowType t) or - TConsNil(TypedContent tc, DataFlowType t) { - Stage4::consCand(tc, TFrontNil(t), _) and - not expensiveLen2unfolding(tc, _) - } or - TConsCons(TypedContent tc1, TypedContent tc2, int len) { - Stage4::consCand(tc1, TFrontHead(tc2), _) and - len in [2 .. accessPathLimit()] and - not expensiveLen2unfolding(tc1, _) - } or - TCons1(TypedContent tc, int len) { - len in [1 .. accessPathLimit()] and - expensiveLen2unfolding(tc, _) - } - -/** - * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only - * the first two elements of the list and its length are tracked. If data flows - * from a source to a given node with a given `AccessPathApprox`, this indicates - * the sequence of dereference operations needed to get from the value in the node - * to the tracked object. The final type indicates the type of the tracked object. - */ -abstract private class AccessPathApprox extends TAccessPathApprox { - abstract string toString(); - - abstract TypedContent getHead(); - - abstract int len(); - - abstract DataFlowType getType(); - - abstract AccessPathFront getFront(); - - /** Gets the access path obtained by popping `head` from this path, if any. */ - abstract AccessPathApprox pop(TypedContent head); -} - -private class AccessPathApproxNil extends AccessPathApprox, TNil { - private DataFlowType t; - - AccessPathApproxNil() { this = TNil(t) } - - override string toString() { result = concat(": " + ppReprType(t)) } - - override TypedContent getHead() { none() } - - override int len() { result = 0 } - - override DataFlowType getType() { result = t } - - override AccessPathFront getFront() { result = TFrontNil(t) } - - override AccessPathApprox pop(TypedContent head) { none() } -} - -abstract private class AccessPathApproxCons extends AccessPathApprox { } - -private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { - private TypedContent tc; - private DataFlowType t; - - AccessPathApproxConsNil() { this = TConsNil(tc, t) } - - override string toString() { - // The `concat` becomes "" if `ppReprType` has no result. - result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) - } - - override TypedContent getHead() { result = tc } - - override int len() { result = 1 } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } -} - -private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { - private TypedContent tc1; - private TypedContent tc2; - private int len; - - AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - - override string toString() { - if len = 2 - then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" - else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc1 } - - override int len() { result = len } - - override DataFlowType getType() { result = tc1.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc1) } - - override AccessPathApprox pop(TypedContent head) { - head = tc1 and - ( - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - } -} - -private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { - private TypedContent tc; - private int len; - - AccessPathApproxCons1() { this = TCons1(tc, len) } - - override string toString() { - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc } - - override int len() { result = len } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { - head = tc and - ( - exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2), _) | - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - or - exists(DataFlowType t | - len = 1 and - Stage4::consCand(tc, TFrontNil(t), _) and - result = TNil(t) - ) - ) - } -} - -/** Gets the access path obtained by popping `tc` from `ap`, if any. */ -private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } - -/** Gets the access path obtained by pushing `tc` onto `ap`. */ -private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } - -private newtype TAccessPathApproxOption = - TAccessPathApproxNone() or - TAccessPathApproxSome(AccessPathApprox apa) - -private class AccessPathApproxOption extends TAccessPathApproxOption { - string toString() { - this = TAccessPathApproxNone() and result = "" - or - this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) - } -} - -private module Stage5Param implements MkStage::StageParam { - private module PrevStage = Stage4; - - class Ap = AccessPathApprox; - - class ApNil = AccessPathApproxNil; - - pragma[nomagic] - PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathApproxOption; - - ApOption apNone() { result = TAccessPathApproxNone() } - - ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } - - import Level1CallContext - import LocalCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, lcc) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { any() } - - // Type checking is not necessary here as it has already been done in stage 3. - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage5 = MkStage::Stage; - -bindingset[conf, result] -private Configuration unbindConf(Configuration conf) { - exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c)) -} - -pragma[nomagic] -private predicate nodeMayUseSummary0( - NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(AccessPathApprox apa0 | - Stage5::parameterMayFlowThrough(p, _, _) and - Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0, config) and - Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), - TAccessPathApproxSome(apa), apa0, config) - ) -} - -pragma[nomagic] -private predicate nodeMayUseSummary( - NodeEx n, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::parameterMayFlowThrough(p, apa, config) and - nodeMayUseSummary0(n, p, state, apa, config) - ) -} - -private newtype TSummaryCtx = - TSummaryCtxNone() or - TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { - exists(Configuration config | - Stage5::parameterMayFlowThrough(p, ap.getApprox(), config) and - Stage5::revFlow(p, state, _, config) - ) - } - -/** - * A context for generating flow summaries. This represents flow entry through - * a specific parameter with an access path of a specific shape. - * - * Summaries are only created for parameters that may flow through. - */ -abstract private class SummaryCtx extends TSummaryCtx { - abstract string toString(); -} - -/** A summary context from which no flow summary can be generated. */ -private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { - override string toString() { result = "" } -} - -/** A summary context from which a flow summary can be generated. */ -private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParamNodeEx p; - private FlowState s; - private AccessPath ap; - - SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } - - ParameterPosition getParameterPos() { p.isParameterOf(_, result) } - - ParamNodeEx getParamNode() { result = p } - - override string toString() { result = p + ": " + ap } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** - * Gets the number of length 2 access path approximations that correspond to `apa`. - */ -private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { - exists(TypedContent tc, int len | - tc = apa.getHead() and - len = apa.len() and - result = - strictcount(AccessPathFront apf | - Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), - config) - ) - ) -} - -private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = - strictcount(NodeEx n, FlowState state | - Stage5::revFlow(n, state, apa, config) or nodeMayUseSummary(n, state, apa, config) - ) -} - -/** - * Holds if a length 2 access path approximation matching `apa` is expected - * to be expensive. - */ -private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = count1to2unfold(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - apLimit < aps and - tupleLimit < (aps - 1) * nodes - ) -} - -private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { - exists(TypedContent head | - apa.pop(head) = result and - Stage5::consCand(head, result, config) - ) -} - -/** - * Holds with `unfold = false` if a precise head-tail representation of `apa` is - * expected to be expensive. Holds with `unfold = true` otherwise. - */ -private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { - if apa.getHead().forceHighPrecision() - then unfold = true - else - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = countPotentialAps(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true - ) -} - -/** - * Gets the number of `AccessPath`s that correspond to `apa`. - */ -pragma[assume_small_delta] -private int countAps(AccessPathApprox apa, Configuration config) { - evalUnfold(apa, false, config) and - result = 1 and - (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) - or - evalUnfold(apa, false, config) and - result = count1to2unfold(apa, config) and - not expensiveLen1to2unfolding(apa, config) - or - evalUnfold(apa, true, config) and - result = countPotentialAps(apa, config) -} - -/** - * Gets the number of `AccessPath`s that would correspond to `apa` assuming - * that it is expanded to a precise head-tail representation. - */ -language[monotonicAggregates] -pragma[assume_small_delta] -private int countPotentialAps(AccessPathApprox apa, Configuration config) { - apa instanceof AccessPathApproxNil and result = 1 - or - result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) -} - -private newtype TAccessPath = - TAccessPathNil(DataFlowType t) or - TAccessPathCons(TypedContent head, AccessPath tail) { - exists(AccessPathApproxCons apa | - not evalUnfold(apa, false, _) and - head = apa.getHead() and - tail.getApprox() = getATail(apa, _) - ) - } or - TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - not expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head1 = apa.getHead() and - head2 = getATail(apa, _).getHead() - ) - } or - TAccessPathCons1(TypedContent head, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head = apa.getHead() - ) - } - -private newtype TPathNode = - pragma[assume_small_delta] - TPathNodeMid( - NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config - ) { - // A PathNode is introduced by a source ... - Stage5::revFlow(node, state, config) and - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - or - // ... or a step from an existing PathNode to another node. - exists(PathNodeMid mid | - pathStep(mid, node, state, cc, sc, ap) and - pragma[only_bind_into](config) = mid.getConfiguration() and - Stage5::revFlow(node, state, ap.getApprox(), pragma[only_bind_into](config)) - ) - } or - TPathNodeSink(NodeEx node, FlowState state, Configuration config) { - exists(PathNodeMid sink | - sink.isAtSink() and - node = sink.getNodeEx() and - state = sink.getState() and - config = sink.getConfiguration() - ) - } or - TPathNodeSourceGroup(string sourceGroup, Configuration config) { - exists(PathNodeImpl source | - sourceGroup = source.getSourceGroup() and - config = source.getConfiguration() - ) - } or - TPathNodeSinkGroup(string sinkGroup, Configuration config) { - exists(PathNodeSink sink | - sinkGroup = sink.getSinkGroup() and - config = sink.getConfiguration() - ) - } - -/** - * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a - * source to a given node with a given `AccessPath`, this indicates the sequence - * of dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ -private class AccessPath extends TAccessPath { - /** Gets the head of this access path, if any. */ - abstract TypedContent getHead(); - - /** Gets the tail of this access path, if any. */ - abstract AccessPath getTail(); - - /** Gets the front of this access path. */ - abstract AccessPathFront getFront(); - - /** Gets the approximation of this access path. */ - abstract AccessPathApprox getApprox(); - - /** Gets the length of this access path. */ - abstract int length(); - - /** Gets a textual representation of this access path. */ - abstract string toString(); - - /** Gets the access path obtained by popping `tc` from this access path, if any. */ - final AccessPath pop(TypedContent tc) { - result = this.getTail() and - tc = this.getHead() - } - - /** Gets the access path obtained by pushing `tc` onto this access path. */ - final AccessPath push(TypedContent tc) { this = result.pop(tc) } -} - -private class AccessPathNil extends AccessPath, TAccessPathNil { - private DataFlowType t; - - AccessPathNil() { this = TAccessPathNil(t) } - - DataFlowType getType() { result = t } - - override TypedContent getHead() { none() } - - override AccessPath getTail() { none() } - - override AccessPathFrontNil getFront() { result = TFrontNil(t) } - - override AccessPathApproxNil getApprox() { result = TNil(t) } - - override int length() { result = 0 } - - override string toString() { result = concat(": " + ppReprType(t)) } -} - -private class AccessPathCons extends AccessPath, TAccessPathCons { - private TypedContent head; - private AccessPath tail; - - AccessPathCons() { this = TAccessPathCons(head, tail) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { result = tail } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - pragma[assume_small_delta] - override AccessPathApproxCons getApprox() { - result = TConsNil(head, tail.(AccessPathNil).getType()) - or - result = TConsCons(head, tail.getHead(), this.length()) - or - result = TCons1(head, this.length()) - } - - pragma[assume_small_delta] - override int length() { result = 1 + tail.length() } - - private string toStringImpl(boolean needsSuffix) { - exists(DataFlowType t | - tail = TAccessPathNil(t) and - needsSuffix = false and - result = head.toString() + "]" + concat(" : " + ppReprType(t)) - ) - or - result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) - or - exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | - result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true - or - result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false - ) - or - exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | - result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true - or - result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false - ) - } - - override string toString() { - result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" - or - result = "[" + this.toStringImpl(false) - } -} - -private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { - private TypedContent head1; - private TypedContent head2; - private int len; - - AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } - - override TypedContent getHead() { result = head1 } - - override AccessPath getTail() { - Stage5::consCand(head1, result.getApprox(), _) and - result.getHead() = head2 and - result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head1) } - - override AccessPathApproxCons getApprox() { - result = TConsCons(head1, head2, len) or - result = TCons1(head1, len) - } - - override int length() { result = len } - - override string toString() { - if len = 2 - then result = "[" + head1.toString() + ", " + head2.toString() + "]" - else - result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" - } -} - -private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { - private TypedContent head; - private int len; - - AccessPathCons1() { this = TAccessPathCons1(head, len) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { - Stage5::consCand(head, result.getApprox(), _) and result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - - override int length() { result = len } - - override string toString() { - if len = 1 - then result = "[" + head.toString() + "]" - else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" - } -} - -abstract private class PathNodeImpl extends TPathNode { - /** Gets the `FlowState` of this node. */ - abstract FlowState getState(); - - /** Gets the associated configuration. */ - abstract Configuration getConfiguration(); - - /** Holds if this node is a source. */ - abstract predicate isSource(); - - abstract PathNodeImpl getASuccessorImpl(); - - private PathNodeImpl getASuccessorIfHidden() { - this.isHidden() and - result = this.getASuccessorImpl() - } - - pragma[nomagic] - private PathNodeImpl getANonHiddenSuccessor0() { - result = this.getASuccessorIfHidden*() and - not result.isHidden() - } - - final PathNodeImpl getANonHiddenSuccessor() { - result = this.getASuccessorImpl().getANonHiddenSuccessor0() and - not this.isHidden() - } - - abstract NodeEx getNodeEx(); - - predicate isHidden() { - not this.getConfiguration().includeHiddenNodes() and - ( - hiddenNode(this.getNodeEx().asNode()) and - not this.isSource() and - not this instanceof PathNodeSink - or - this.getNodeEx() instanceof TNodeImplicitRead - ) - } - - string getSourceGroup() { - this.isSource() and - this.getConfiguration().sourceGrouping(this.getNodeEx().asNode(), result) + not singleConfiguration() and + getConfig(state1).isAdditionalFlowStep(node1, node2) and + state2 = state1 } - predicate isFlowSource() { - this.isSource() and not exists(this.getSourceGroup()) - or - this instanceof PathNodeSourceGroup + predicate allowImplicitRead(Node node, ContentSet c) { + any(Configuration config).allowImplicitRead(node, c) } - predicate isFlowSink() { - this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or - this instanceof PathNodeSinkGroup - } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } - private string ppAp() { - this instanceof PathNodeSink and result = "" - or - exists(string s | s = this.(PathNodeMid).getAp().toString() | - if s = "" then result = "" else result = " " + s - ) - } + FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } - private string ppCtx() { - this instanceof PathNodeSink and result = "" - or - result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + predicate sourceGrouping(Node source, string sourceGroup) { + any(Configuration config).sourceGrouping(source, sourceGroup) } - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + predicate sinkGrouping(Node sink, string sinkGroup) { + any(Configuration config).sinkGrouping(sink, sinkGroup) } -} - -/** Holds if `n` can reach a sink. */ -private predicate directReach(PathNodeImpl n) { - n instanceof PathNodeSink or - n instanceof PathNodeSinkGroup or - directReach(n.getANonHiddenSuccessor()) -} - -/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ -private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } -/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { - n1.getANonHiddenSuccessor() = n2 and directReach(n2) + predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() } } -private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) +private import Impl as I +import I /** * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. */ -class PathNode instanceof PathNodeImpl { - PathNode() { reach(this) } - +class PathNode instanceof I::PathNode { /** Gets a textual representation of this element. */ final string toString() { result = super.toString() } @@ -3406,1548 +358,39 @@ class PathNode instanceof PathNodeImpl { } /** Gets the underlying `Node`. */ - final Node getNode() { super.getNodeEx().projectToNode() = result } + final Node getNode() { result = super.getNode() } /** Gets the `FlowState` of this node. */ - final FlowState getState() { result = super.getState() } + final FlowState getState() { result = getState(super.getState()) } /** Gets the associated configuration. */ - final Configuration getConfiguration() { result = super.getConfiguration() } + final Configuration getConfiguration() { result = getConfig(super.getState()) } /** Gets a successor of this node, if any. */ - final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } + final PathNode getASuccessor() { result = super.getASuccessor() } /** Holds if this node is a source. */ final predicate isSource() { super.isSource() } /** Holds if this node is a grouping of source nodes. */ - final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group, _) } + final predicate isSourceGroup(string group) { super.isSourceGroup(group) } /** Holds if this node is a grouping of sink nodes. */ - final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group, _) } + final predicate isSinkGroup(string group) { super.isSinkGroup(group) } } -/** - * Provides the query predicates needed to include a graph in a path-problem query. - */ -module PathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } - - /** Holds if `n` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode n, string key, string val) { - key = "semmle.label" and val = n.toString() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { - Subpaths::subpaths(arg, par, ret, out) - } -} - -/** - * An intermediate flow graph node. This is a triple consisting of a `Node`, - * a `CallContext`, and a `Configuration`. - */ -private class PathNodeMid extends PathNodeImpl, TPathNodeMid { - NodeEx node; - FlowState state; - CallContext cc; - SummaryCtx sc; - AccessPath ap; - Configuration config; - - PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - SummaryCtx getSummaryCtx() { result = sc } - - AccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - private PathNodeMid getSuccMid() { - pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx(), result.getAp()) and - result.getConfiguration() = unbindConf(this.getConfiguration()) - } - - override PathNodeImpl getASuccessorImpl() { - // an intermediate step to another intermediate node - result = this.getSuccMid() - or - // a final step to a sink - result = this.getSuccMid().projectToSink() - } - - override predicate isSource() { - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - } - - predicate isAtSink() { - sinkNode(node, state, config) and - ap instanceof AccessPathNil and - if hasSinkCallCtx(config) - then - // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` - // is exactly what we need to check. This also implies - // `sc instanceof SummaryCtxNone`. - // For `FeatureEqualSourceSinkCallContext` the initial call context was - // set to `CallContextSomeCall` and jumps are disallowed, so - // `cc instanceof CallContextNoCall` never holds. On the other hand, - // in this case there's never any need to enter a call except to identify - // a summary, so the condition in `pathIntoCallable` enforces this, which - // means that `sc instanceof SummaryCtxNone` holds if and only if we are - // in the call context of the source. - sc instanceof SummaryCtxNone or - cc instanceof CallContextNoCall - else any() - } - - PathNodeSink projectToSink() { - this.isAtSink() and - result.getNodeEx() = node and - result.getState() = state and - result.getConfiguration() = unbindConf(config) - } -} - -/** - * A flow graph node corresponding to a sink. This is disjoint from the - * intermediate nodes in order to uniquely correspond to a given sink by - * excluding the `CallContext`. - */ -private class PathNodeSink extends PathNodeImpl, TPathNodeSink { - NodeEx node; - FlowState state; - Configuration config; - - PathNodeSink() { this = TPathNodeSink(node, state, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result = TPathNodeSinkGroup(this.getSinkGroup(), config) - } - - override predicate isSource() { sourceNode(node, state, config) } - - string getSinkGroup() { config.sinkGrouping(node.asNode(), result) } -} - -private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { - string sourceGroup; - Configuration config; - - PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result.getSourceGroup() = sourceGroup and - result.getConfiguration() = config - } - - override predicate isSource() { none() } - - override string toString() { result = sourceGroup } - - override 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 - } -} - -private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { - string sinkGroup; - Configuration config; - - PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { none() } - - override predicate isSource() { none() } - - override string toString() { result = sinkGroup } - - override 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 - } -} - -private predicate pathNode( - PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, - Configuration conf, LocalCallContext localCC -) { - midnode = mid.getNodeEx() and - state = mid.getState() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - midnode.getEnclosingCallable()) and - ap = mid.getAp() -} - -/** - * Holds if data may flow from `mid` to `node`. The last step in or out of - * a callable is recorded by `cc`. - */ -pragma[assume_small_delta] -pragma[nomagic] -private predicate pathStep( - PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap -) { - exists(NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC | - pathNode(mid, midnode, state0, cc, sc, ap, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, true, _, conf, localCC) - ) - or - exists( - AccessPath ap0, NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC - | - pathNode(mid, midnode, state0, cc, sc, ap0, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), conf, - localCC) and - ap0 instanceof AccessPathNil - ) - or - jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - ap = mid.getAp() - or - additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, mid.getConfiguration()) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - pathIntoCallable(mid, node, state, _, cc, sc, _, _) and ap = mid.getAp() - or - pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone - or - pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() -} - -pragma[nomagic] -private predicate pathReadStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - tc = ap0.getHead() and - Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -pragma[nomagic] -private predicate pathStoreStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -private predicate pathOutOfCallable0( - PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa, - Configuration config -) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - apa = mid.getAp().getApprox() and - config = mid.getConfiguration() -} - -pragma[nomagic] -private predicate pathOutOfCallable1( - PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPathApprox apa, Configuration config -) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - pathOutOfCallable0(mid, pos, state, innercc, apa, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() +private predicate hasFlow(Node source, Node sink, Configuration config) { + exists(PathNode source0, PathNode sink0 | + hasFlowPath(source0, sink0, config) and + source0.getNode() = source and + sink0.getNode() = sink ) } -pragma[noinline] -private NodeEx getAnOutNodeFlow( - ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config -) { - result.asNode() = kind.getAnOutNode(call) and - Stage5::revFlow(result, _, apa, config) +private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) { + hasFlowPath(source, sink) and source.getConfiguration() = config } -/** - * Holds if data may flow from `mid` to `out`. The last step of this path - * is a return from a callable and is recorded by `cc`, if needed. - */ -pragma[noinline] -private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { - exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | - pathOutOfCallable1(mid, call, kind, state, cc, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} +private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) } -/** - * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. - */ -pragma[noinline] -private predicate pathIntoArg( - PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(ArgNodeEx arg, ArgumentPosition apos | - pathNode(mid, arg, state, cc, _, ap, config, _) and - arg.asNode().(ArgNode).argumentOf(call, apos) and - apa = ap.getApprox() and - parameterMatch(ppos, apos) - ) -} - -pragma[nomagic] -private predicate parameterCand( - DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::revFlow(p, _, apa, config) and - p.isParameterOf(callable, pos) - ) -} - -pragma[nomagic] -private predicate pathIntoCallable0( - PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, AccessPath ap, Configuration config -) { - exists(AccessPathApprox apa | - pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - callable = resolveCall(call, outercc) and - parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa), - pragma[only_bind_into](config)) - ) -} - -/** - * Holds if data may flow from `mid` to `p` through `call`. The contexts - * before and after entering the callable are `outercc` and `innercc`, - * respectively. - */ -pragma[nomagic] -private predicate pathIntoCallable( - PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, - SummaryCtx sc, DataFlowCall call, Configuration config -) { - exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | - pathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - ( - sc = TSummaryCtxSome(p, state, ap) - or - not exists(TSummaryCtxSome(p, state, ap)) and - sc = TSummaryCtxNone() and - // When the call contexts of source and sink needs to match then there's - // never any reason to enter a callable except to find a summary. See also - // the comment in `PathNodeMid::isAtSink`. - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) -} - -/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ -pragma[nomagic] -private predicate paramFlowsThrough( - ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, - AccessPathApprox apa, Configuration config -) { - exists(RetNodeEx ret | - pathNode(_, ret, state, cc, sc, ap, config, _) and - kind = ret.getKind() and - apa = ap.getApprox() and - parameterFlowThroughAllowed(sc.getParamNode(), kind) - ) -} - -pragma[nomagic] -private predicate pathThroughCallable0( - DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(CallContext innercc, SummaryCtx sc | - pathIntoCallable(mid, _, _, cc, innercc, sc, call, config) and - paramFlowsThrough(kind, state, innercc, sc, ap, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` through a callable to the node `out`. - * The context `cc` is restored to its value prior to entering the callable. - */ -pragma[noinline] -private predicate pathThroughCallable( - PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap -) { - exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config | - pathThroughCallable0(call, mid, kind, state, cc, ap, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -private module Subpaths { - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths01( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - exists(Configuration config | - pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and - pathIntoCallable(arg, par, _, _, innercc, sc, _, config) and - paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, - pragma[only_bind_into](apout), _, unbindConf(config)) and - not arg.isHidden() - ) - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `sout`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths02( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and - out.asNode() = kind.getAnOutNode(_) - } - - pragma[nomagic] - private Configuration getPathNodeConf(PathNodeImpl n) { result = n.getConfiguration() } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple. - */ - pragma[nomagic] - private predicate subpaths03( - PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, AccessPath apout - ) { - exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | - subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and - pathNode(ret, retnode, sout, innercc, sc, apout, unbindConf(getPathNodeConf(arg)), _) and - kind = retnode.getKind() - ) - } - - private PathNodeImpl localStepToHidden(PathNodeImpl n) { - n.getASuccessorImpl() = result and - result.isHidden() and - exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | - localFlowBigStep(n1, _, n2, _, _, _, _, _) or - store(n1, _, n2, _, _) or - readSet(n1, _, n2, _) - ) - } - - pragma[nomagic] - private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { - succ = pred.getANonHiddenSuccessor() and - succNode = succ.getNodeEx() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { - exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | - pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and - subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and - hasSuccessor(pragma[only_bind_into](arg), par, p) and - not ret.isHidden() and - pathNode(out0, o, sout, _, _, apout, _, _) - | - out = out0 or out = out0.projectToSink() - ) - } - - /** - * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. - */ - predicate retReach(PathNodeImpl n) { - exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) - or - exists(PathNodeImpl mid | - retReach(mid) and - n.getANonHiddenSuccessor() = mid and - not subpaths(_, mid, _, _) - ) - } -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -private predicate hasFlowPath( - PathNodeImpl flowsource, PathNodeImpl flowsink, Configuration configuration -) { - flowsource.isFlowSource() and - flowsource.getConfiguration() = configuration and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.isFlowSink() -} - -private predicate flowsTo( - PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink, - Configuration configuration -) { - flowsource.isSource() and - flowsource.getConfiguration() = configuration and - flowsource.getNodeEx().asNode() = source and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.getNodeEx().asNode() = sink -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -predicate flowsTo(Node source, Node sink, Configuration configuration) { - flowsTo(_, _, source, sink, configuration) -} - -private predicate finalStats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples -) { - fwd = true and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and - tuples = count(PathNodeImpl pn) - or - fwd = false and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and - tuples = count(PathNode pn) -} - -/** - * INTERNAL: Only for debugging. - * - * Calculates per-stage metrics for data flow. - */ -predicate stageStats( - int n, string stage, int nodes, int fields, int conscand, int states, int tuples, - Configuration config -) { - stage = "1 Fwd" and - n = 10 and - Stage1::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "1 Rev" and - n = 15 and - Stage1::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Fwd" and - n = 20 and - Stage2::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Rev" and - n = 25 and - Stage2::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Fwd" and - n = 30 and - Stage3::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Rev" and - n = 35 and - Stage3::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Fwd" and - n = 40 and - Stage4::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Rev" and - n = 45 and - Stage4::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Fwd" and - n = 50 and - Stage5::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Rev" and - n = 55 and - Stage5::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) - or - stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) -} - -private module FlowExploration { - private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - jumpStep(node1, node2, config) - or - additionalJumpStep(node1, node2, config) - or - additionalJumpStateStep(node1, _, node2, _, config) - or - // flow into callable - viableParamArgEx(_, node2, node1) - or - // flow out of a callable - viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) - | - c1 = node1.getEnclosingCallable() and - c2 = node2.getEnclosingCallable() and - c1 != c2 - ) - } - - private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSource(n) or config.isSource(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSrc(mid, config) and callableStep(mid, c, config) - ) - } - - private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSink(n) or config.isSink(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSink(mid, config) and callableStep(c, mid, config) - ) - } - - private newtype TCallableExt = - TCallable(DataFlowCallable c, Configuration config) { - interestingCallableSrc(c, config) or - interestingCallableSink(c, config) - } or - TCallableSrc() or - TCallableSink() - - private predicate callableExtSrc(TCallableSrc src) { any() } - - private predicate callableExtSink(TCallableSink sink) { any() } - - private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { - exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | - callableStep(c1, c2, config) and - ce1 = TCallable(c1, pragma[only_bind_into](config)) and - ce2 = TCallable(c2, pragma[only_bind_into](config)) - ) - or - exists(Node n, Configuration config | - ce1 = TCallableSrc() and - (config.isSource(n) or config.isSource(n, _)) and - ce2 = TCallable(getNodeEnclosingCallable(n), config) - ) - or - exists(Node n, Configuration config | - ce2 = TCallableSink() and - (config.isSink(n) or config.isSink(n, _)) and - ce1 = TCallable(getNodeEnclosingCallable(n), config) - ) - } - - private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { - callableExtStepFwd(ce2, ce1) - } - - private int distSrcExt(TCallableExt c) = - shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) - - private int distSinkExt(TCallableExt c) = - shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) - - private int distSrc(DataFlowCallable c, Configuration config) { - result = distSrcExt(TCallable(c, config)) - 1 - } - - private int distSink(DataFlowCallable c, Configuration config) { - result = distSinkExt(TCallable(c, config)) - 1 - } - - private newtype TPartialAccessPath = - TPartialNil(DataFlowType t) or - TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first - * element of the list and its length are tracked. If data flows from a source to - * a given node with a given `AccessPath`, this indicates the sequence of - * dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ - private class PartialAccessPath extends TPartialAccessPath { - abstract string toString(); - - TypedContent getHead() { this = TPartialCons(result, _) } - - int len() { - this = TPartialNil(_) and result = 0 - or - this = TPartialCons(_, result) - } - - DataFlowType getType() { - this = TPartialNil(result) - or - exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) - } - } - - private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { - override string toString() { - exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) - } - } - - private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { - override string toString() { - exists(TypedContent tc, int len | this = TPartialCons(tc, len) | - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private newtype TRevPartialAccessPath = - TRevPartialNil() or - TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `Content`s, but only the first - * element of the list and its length are tracked. - */ - private class RevPartialAccessPath extends TRevPartialAccessPath { - abstract string toString(); - - Content getHead() { this = TRevPartialCons(result, _) } - - int len() { - this = TRevPartialNil() and result = 0 - or - this = TRevPartialCons(_, result) - } - } - - private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { - override string toString() { result = "" } - } - - private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { - override string toString() { - exists(Content c, int len | this = TRevPartialCons(c, len) | - if len = 1 - then result = "[" + c.toString() + "]" - else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private predicate relevantState(FlowState state) { - sourceNode(_, state, _) or - sinkNode(_, state, _) or - additionalLocalStateStep(_, state, _, _, _) or - additionalLocalStateStep(_, _, _, state, _) or - additionalJumpStateStep(_, state, _, _, _) or - additionalJumpStateStep(_, _, _, state, _) - } - - private newtype TSummaryCtx1 = - TSummaryCtx1None() or - TSummaryCtx1Param(ParamNodeEx p) - - private newtype TSummaryCtx2 = - TSummaryCtx2None() or - TSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TSummaryCtx3 = - TSummaryCtx3None() or - TSummaryCtx3Some(PartialAccessPath ap) - - private newtype TRevSummaryCtx1 = - TRevSummaryCtx1None() or - TRevSummaryCtx1Some(ReturnPosition pos) - - private newtype TRevSummaryCtx2 = - TRevSummaryCtx2None() or - TRevSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TRevSummaryCtx3 = - TRevSummaryCtx3None() or - TRevSummaryCtx3Some(RevPartialAccessPath ap) - - private newtype TPartialPathNode = - TPartialPathNodeFwd( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = TPartialNil(node.getDataFlowType()) and - exists(config.explorationLimit()) - or - partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap, config) and - distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() - } or - TPartialPathNodeRev( - NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, - RevPartialAccessPath ap, Configuration config - ) { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() and - exists(config.explorationLimit()) - or - revPartialPathStep(_, node, state, sc1, sc2, sc3, ap, config) and - not clearsContentEx(node, ap.getHead()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead()) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() - } - - pragma[nomagic] - private predicate partialPathNodeMk0( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap, config) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - not clearsContentEx(node, ap.getHead().getContent()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead().getContent()) - ) and - if node.asNode() instanceof CastingNode - then compatibleTypes(node.getDataFlowType(), ap.getType()) - else any() - } - - /** - * A `Node` augmented with a call context, an access path, and a configuration. - */ - class PartialPathNode extends TPartialPathNode { - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() - } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - /** Gets the underlying `Node`. */ - final Node getNode() { this.getNodeEx().projectToNode() = result } - - FlowState getState() { none() } - - private NodeEx getNodeEx() { - result = this.(PartialPathNodeFwd).getNodeEx() or - result = this.(PartialPathNodeRev).getNodeEx() - } - - /** Gets the associated configuration. */ - Configuration getConfiguration() { none() } - - /** Gets a successor of this node, if any. */ - PartialPathNode getASuccessor() { none() } - - /** - * Gets the approximate distance to the nearest source measured in number - * of interprocedural steps. - */ - int getSourceDistance() { - result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - /** - * Gets the approximate distance to the nearest sink measured in number - * of interprocedural steps. - */ - int getSinkDistance() { - result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - private string ppAp() { - exists(string s | - s = this.(PartialPathNodeFwd).getAp().toString() or - s = this.(PartialPathNodeRev).getAp().toString() - | - if s = "" then result = "" else result = " " + s - ) - } - - private string ppCtx() { - result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" - } - - /** Holds if this is a source in a forward-flow path. */ - predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } - - /** Holds if this is a sink in a reverse-flow path. */ - predicate isRevSink() { this.(PartialPathNodeRev).isSink() } - } - - /** - * Provides the query predicates needed to include a graph in a path-problem query. - */ - module PartialPathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } - } - - private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { - NodeEx node; - FlowState state; - CallContext cc; - TSummaryCtx1 sc1; - TSummaryCtx2 sc2; - TSummaryCtx3 sc3; - PartialAccessPath ap; - Configuration config; - - PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - TSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TSummaryCtx3 getSummaryCtx3() { result = sc3 } - - PartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeFwd getASuccessor() { - partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp(), - result.getConfiguration()) - } - - predicate isSource() { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap instanceof TPartialNil - } - } - - private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { - NodeEx node; - FlowState state; - TRevSummaryCtx1 sc1; - TRevSummaryCtx2 sc2; - TRevSummaryCtx3 sc3; - RevPartialAccessPath ap; - Configuration config; - - PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } - - RevPartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeRev getASuccessor() { - revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), - this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp(), this.getConfiguration()) - } - - predicate isSink() { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() - } - } - - private predicate partialPathStep( - PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, - TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and - ( - localFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - ) - or - jumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - partialPathStoreStep(mid, _, _, node, ap) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(PartialAccessPath ap0, TypedContent tc | - partialPathReadStep(mid, ap0, tc, node, cc, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsFwd(ap, tc, ap0, config) - ) - or - partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap, config) - or - partialPathOutOfCallable(mid, node, state, cc, ap, config) and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() - or - partialPathThroughCallable(mid, node, state, cc, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - bindingset[result, i] - private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } - - pragma[inline] - private predicate partialPathStoreStep( - PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, - PartialAccessPath ap2 - ) { - exists(NodeEx midNode, DataFlowType contentType | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - store(midNode, tc, node, contentType, mid.getConfiguration()) and - ap2.getHead() = tc and - ap2.len() = unbindInt(ap1.len() + 1) and - compatibleTypes(ap1.getType(), contentType) - ) - } - - pragma[nomagic] - private predicate apConsFwd( - PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeFwd mid | - partialPathStoreStep(mid, ap1, tc, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate partialPathReadStep( - PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc, - Configuration config - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and - ap.getHead() = tc and - pragma[only_bind_into](config) = mid.getConfiguration() and - cc = mid.getCallContext() - ) - } - - private predicate partialPathOutOfCallable0( - PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, - PartialAccessPath ap, Configuration config - ) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - ap = mid.getAp() and - config = mid.getConfiguration() - } - - pragma[nomagic] - private predicate partialPathOutOfCallable1( - PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - partialPathOutOfCallable0(mid, pos, state, innercc, ap, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) - } - - private predicate partialPathOutOfCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(ReturnKindExt kind, DataFlowCall call | - partialPathOutOfCallable1(mid, call, kind, state, cc, ap, config) - | - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[noinline] - private predicate partialPathIntoArg( - PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ArgNode arg, ArgumentPosition apos | - arg = mid.getNodeEx().asNode() and - state = mid.getState() and - cc = mid.getCallContext() and - arg.argumentOf(call, apos) and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate partialPathIntoCallable0( - PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - partialPathIntoArg(mid, pos, state, outercc, call, ap, config) and - callable = resolveCall(call, outercc) - } - - private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, - CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ParameterPosition pos, DataFlowCallable callable | - partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - sc1 = TSummaryCtx1Param(p) and - sc2 = TSummaryCtx2Some(state) and - sc3 = TSummaryCtx3Some(ap) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) - } - - pragma[nomagic] - private predicate paramFlowsThroughInPartialPath( - ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeFwd mid, RetNodeEx ret | - mid.getNodeEx() = ret and - kind = ret.getKind() and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() and - ap = mid.getAp() - ) - } - - pragma[noinline] - private predicate partialPathThroughCallable0( - DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | - partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _, config) and - paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap, config) - ) - } - - private predicate partialPathThroughCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ReturnKindExt kind | - partialPathThroughCallable0(call, mid, kind, state, cc, ap, config) and - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[nomagic] - private predicate revPartialPathStep( - PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, - TRevSummaryCtx3 sc3, RevPartialAccessPath ap, Configuration config - ) { - localFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - jumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - revPartialPathReadStep(mid, _, _, node, ap) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(RevPartialAccessPath ap0, Content c | - revPartialPathStoreStep(mid, ap0, c, node, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsRev(ap, c, ap0, config) - ) - or - exists(ParamNodeEx p | - mid.getNodeEx() = p and - viableParamArgEx(_, p, node) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - or - exists(ReturnPosition pos | - revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap, config) and - pos = getReturnPosition(node.asNode()) - ) - or - revPartialPathThroughCallable(mid, node, state, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - pragma[inline] - private predicate revPartialPathReadStep( - PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, - RevPartialAccessPath ap2 - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - read(node, c, midNode, mid.getConfiguration()) and - ap2.getHead() = c and - ap2.len() = unbindInt(ap1.len() + 1) - ) - } - - pragma[nomagic] - private predicate apConsRev( - RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeRev mid | - revPartialPathReadStep(mid, ap1, c, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathStoreStep( - PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config - ) { - exists(NodeEx midNode, TypedContent tc | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - store(node, tc, midNode, _, config) and - ap.getHead() = c and - config = mid.getConfiguration() and - tc.getContent() = c - ) - } - - pragma[nomagic] - private predicate revPartialPathIntoReturn( - PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, - TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap, - Configuration config - ) { - exists(NodeEx out | - mid.getNodeEx() = out and - mid.getState() = state and - viableReturnPosOutEx(call, pos, out) and - sc1 = TRevSummaryCtx1Some(pos) and - sc2 = TRevSummaryCtx2Some(state) and - sc3 = TRevSummaryCtx3Some(ap) and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathFlowsThrough( - ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, - TRevSummaryCtx3Some sc3, RevPartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | - mid.getNodeEx() = p and - mid.getState() = state and - p.getPosition() = ppos and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable0( - DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, - RevPartialAccessPath ap, Configuration config - ) { - exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | - revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _, config) and - revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap, config) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ArgumentPosition pos | - revPartialPathThroughCallable0(call, mid, pos, state, ap, config) and - node.asNode().(ArgNode).argumentOf(call, pos) - ) - } -} - -import FlowExploration - -private predicate partialFlow( - PartialPathNode source, PartialPathNode node, Configuration configuration -) { - source.getConfiguration() = configuration and - source.isFwdSource() and - node = source.getASuccessor+() -} - -private predicate revPartialFlow( - PartialPathNode node, PartialPathNode sink, Configuration configuration -) { - sink.getConfiguration() = configuration and - sink.isRevSink() and - node.getASuccessor+() = sink -} +predicate flowsTo = hasFlow/3; diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll index 1b969756b09..e6bdc74cceb 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll @@ -1,16 +1,16 @@ /** - * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from - * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed - * through the `Configuration` class. This file exists in several identical - * copies, allowing queries to use multiple `Configuration` classes that depend - * on each other without introducing mutual recursion among those configurations. + * DEPRECATED: Use `Make` and `MakeWithState` instead. + * + * Provides a `Configuration` class backwards-compatible interface to the data + * flow library. */ private import DataFlowImplCommon private import DataFlowImplSpecific::Private import DataFlowImplSpecific::Public +private import DataFlowImpl import DataFlowImplCommonPublic +import FlowStateString /** * A configuration of interprocedural data flow analysis. This defines @@ -144,6 +144,10 @@ abstract class Configuration extends string { * - `FeatureEqualSourceSinkCallContext`: * Implies both of the above and additionally ensures that the entire flow * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. */ FlowFeature getAFeature() { none() } @@ -156,7 +160,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from `source` to `sink` for this configuration. */ - predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } + predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) } /** * Holds if data may flow from `source` to `sink` for this configuration. @@ -169,9 +173,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from some source to `sink` for this configuration. */ - predicate hasFlowTo(Node sink) { - sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode() - } + predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) } /** * Holds if data may flow from some source to `sink` for this configuration. @@ -179,10 +181,12 @@ abstract class Configuration extends string { predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } /** + * DEPRECATED: Use `FlowExploration` instead. + * * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` * measured in approximate number of interprocedural steps. */ - int explorationLimit() { none() } + deprecated int explorationLimit() { none() } /** * Holds if hidden nodes should be included in the data flow graph. @@ -191,49 +195,6 @@ abstract class Configuration extends string { * is not visualized (for example in a `path-problem` query). */ predicate includeHiddenNodes() { none() } - - /** - * Holds if there is a partial data flow path from `source` to `node`. The - * approximate distance between `node` and the closest source is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards sink definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sources is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - */ - final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { - partialFlow(source, node, this) and - dist = node.getSourceDistance() - } - - /** - * Holds if there is a partial data flow path from `node` to `sink`. The - * approximate distance between `node` and the closest sink is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards source definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sinks is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - * - * Note that reverse flow has slightly lower precision than the corresponding - * forward flow, as reverse flow disregards type pruning among other features. - */ - final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { - revPartialFlow(node, sink, this) and - dist = node.getSinkDistance() - } } /** @@ -263,90 +224,6 @@ abstract private class ConfigurationRecursionPrevention extends Configuration { } } -private newtype TNodeEx = - TNodeNormal(Node n) or - TNodeImplicitRead(Node n, boolean hasRead) { - any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true] - } - -private class NodeEx extends TNodeEx { - string toString() { - result = this.asNode().toString() - or - exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") - } - - Node asNode() { this = TNodeNormal(result) } - - predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } - - Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } - - pragma[nomagic] - private DataFlowCallable getEnclosingCallable0() { - nodeEnclosingCallable(this.projectToNode(), result) - } - - pragma[inline] - DataFlowCallable getEnclosingCallable() { - pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) - } - - pragma[nomagic] - private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } - - pragma[inline] - DataFlowType getDataFlowType() { - pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) - } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -private class ArgNodeEx extends NodeEx { - ArgNodeEx() { this.asNode() instanceof ArgNode } -} - -private class ParamNodeEx extends NodeEx { - ParamNodeEx() { this.asNode() instanceof ParamNode } - - predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { - this.asNode().(ParamNode).isParameterOf(c, pos) - } - - ParameterPosition getPosition() { this.isParameterOf(_, result) } -} - -private class RetNodeEx extends NodeEx { - RetNodeEx() { this.asNode() instanceof ReturnNodeExt } - - ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } - - ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } -} - -private predicate inBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierIn(n) - | - config.isSource(n) or config.isSource(n, _) - ) -} - -private predicate outBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierOut(n) - | - config.isSink(n) or config.isSink(n, _) - ) -} - /** A bridge class to access the deprecated `isBarrierGuard`. */ private class BarrierGuardGuardedNodeBridge extends Unit { abstract predicate guardedNode(Node n, Configuration config); @@ -370,3019 +247,94 @@ private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { } } -pragma[nomagic] -private predicate fullBarrier(NodeEx node, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n) +private FlowState relevantState(Configuration config) { + config.isSource(_, result) or + config.isSink(_, result) or + config.isBarrier(_, result) or + config.isAdditionalFlowStep(_, result, _, _) or + config.isAdditionalFlowStep(_, _, _, result) +} + +private newtype TConfigState = + TMkConfigState(Configuration config, FlowState state) { + state = relevantState(config) or state instanceof FlowStateEmpty + } + +private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) } + +private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) } + +private predicate singleConfiguration() { 1 = strictcount(Configuration c) } + +private module Config implements FullStateConfigSig { + class FlowState = TConfigState; + + predicate isSource(Node source, FlowState state) { + getConfig(state).isSource(source, getState(state)) or - config.isBarrierIn(n) and - not config.isSource(n) and - not config.isSource(n, _) + getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty + } + + predicate isSink(Node sink, FlowState state) { + getConfig(state).isSink(sink, getState(state)) or - config.isBarrierOut(n) and - not config.isSink(n) and - not config.isSink(n, _) + getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty + } + + predicate isBarrier(Node node) { none() } + + predicate isBarrier(Node node, FlowState state) { + getConfig(state).isBarrier(node, getState(state)) or + getConfig(state).isBarrier(node) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state)) + } + + predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) } + + predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) } + + predicate isAdditionalFlowStep(Node node1, Node node2) { + singleConfiguration() and + any(Configuration config).isAdditionalFlowStep(node1, node2) + } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and + getConfig(state2) = getConfig(state1) or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config) - ) -} - -pragma[nomagic] -private predicate stateBarrier(NodeEx node, FlowState state, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n, state) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config) - ) -} - -pragma[nomagic] -private predicate sourceNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSource(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSource(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -pragma[nomagic] -private predicate sinkNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSink(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSink(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -/** Provides the relevant barriers for a step from `node1` to `node2`. */ -pragma[inline] -private predicate stepFilter(NodeEx node1, NodeEx node2, Configuration config) { - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) -} - -/** - * Holds if data can flow in one local step from `node1` to `node2`. - */ -private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.asNode() = n and - node2.isImplicitReadNode(n, false) and - not fullBarrier(node1, config) - ) -} - -/** - * Holds if the additional step from `node1` to `node2` does not jump between callables. - */ -private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.isImplicitReadNode(n, true) and - node2.asNode() = n and - not fullBarrier(node2, config) - ) -} - -private predicate additionalLocalStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) - ) -} - -/** - * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. - */ -private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if the additional step from `node1` to `node2` jumps between callables. - */ -private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate additionalJumpStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -pragma[nomagic] -private predicate readSet(NodeEx node1, ContentSet c, NodeEx node2, Configuration config) { - readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and - stepFilter(node1, node2, config) - or - exists(Node n | - node2.isImplicitReadNode(n, true) and - node1.isImplicitReadNode(n, _) and - config.allowImplicitRead(n, c) - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(ContentSet cs | - readSet(node1, cs, node2, config) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate clearsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - clearsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate expectsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - expectsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -pragma[nomagic] -private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } - -pragma[nomagic] -private predicate hasReadStep(Content c, Configuration config) { read(_, c, _, config) } - -pragma[nomagic] -private predicate store( - NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config -) { - store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), - contentType) and - hasReadStep(tc.getContent(), config) and - stepFilter(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { - viableReturnPosOut(call, pos, out.asNode()) -} - -pragma[nomagic] -private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { - viableParamArg(call, p.asNode(), arg.asNode()) -} - -/** - * Holds if field flow should be used for the given configuration. - */ -private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } - -private predicate hasSourceCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSourceCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate hasSinkCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSinkCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if flow from `p` to a return node of kind `kind` is allowed. - * - * We don't expect a parameter to return stored in itself, unless - * explicitly allowed - */ -bindingset[p, kind] -private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { - exists(ParameterPosition pos | p.isParameterOf(_, pos) | - not kind.(ParamUpdateReturnKind).getPosition() = pos - or - allowParameterReturnInSelfCached(p.asNode()) - ) -} - -private module Stage1 implements StageSig { - class Ap extends int { - // workaround for bad functionality-induced joins (happens when using `Unit`) - pragma[nomagic] - Ap() { this in [0 .. 1] and this < 1 } - } - - private class Cc = boolean; - - /* Begin: Stage 1 logic. */ - /** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `cc` records whether the node is reached through an - * argument in a call. - */ - private predicate fwdFlow(NodeEx node, Cc cc, Configuration config) { - sourceNode(node, _, config) and - if hasSourceCallCtx(config) then cc = true else cc = false - or - exists(NodeEx mid | fwdFlow(mid, cc, config) | - localFlowStep(mid, node, config) or - additionalLocalFlowStep(mid, node, config) or - additionalLocalStateStep(mid, _, node, _, config) - ) - or - exists(NodeEx mid | fwdFlow(mid, _, config) and cc = false | - jumpStep(mid, node, config) or - additionalJumpStep(mid, node, config) or - additionalJumpStateStep(mid, _, node, _, config) - ) - or - // store - exists(NodeEx mid | - useFieldFlow(config) and - fwdFlow(mid, cc, config) and - store(mid, _, node, _, config) - ) - or - // read - exists(ContentSet c | - fwdFlowReadSet(c, node, cc, config) and - fwdFlowConsCandSet(c, _, config) - ) - or - // flow into a callable - fwdFlowIn(_, _, _, node, config) and - cc = true - or - // flow out of a callable - fwdFlowOut(_, node, false, config) and - cc = false - or - // flow through a callable - exists(DataFlowCall call | - fwdFlowOutFromArg(call, node, config) and - fwdFlowIsEntered(call, cc, config) - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowIn( - DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p, Configuration config - ) { - // call context cannot help reduce virtual dispatch - fwdFlow(arg, cc, config) and - viableParamArgEx(call, p, arg) and - not fullBarrier(p, config) and - ( - cc = false - or - cc = true and - not reducedViableImplInCallContext(call, _, _) - ) - or - // call context may help reduce virtual dispatch - exists(DataFlowCallable target | - fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target, config) and - target = viableImplInSomeFwdFlowCallContextExt(call, config) and - cc = true - ) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - fwdFlowIn(call, _, cc, _, config) - } - - pragma[nomagic] - private predicate fwdFlowInReducedViableImplInSomeCallContext( - DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target, Configuration config - ) { - fwdFlow(arg, true, config) and - viableParamArgEx(call, p, arg) and - reducedViableImplInCallContext(call, _, _) and - target = p.getEnclosingCallable() and - not fullBarrier(p, config) - } - - /** - * Gets a viable dispatch target of `call` in the context `ctx`. This is - * restricted to those `call`s for which a context might make a difference, - * and to `ctx`s that are reachable in `fwdFlow`. - */ - pragma[nomagic] - private DataFlowCallable viableImplInSomeFwdFlowCallContextExt( - DataFlowCall call, Configuration config - ) { - exists(DataFlowCall ctx | - fwdFlowIsEntered(ctx, _, config) and - result = viableImplInCallContextExt(call, ctx) - ) - } - - private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) } - - pragma[nomagic] - private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc, Configuration config) { - exists(NodeEx mid | - fwdFlow(mid, cc, config) and - readSet(mid, c, node, config) - ) - } - - /** - * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - fwdFlow(mid, _, config) and - store(mid, tc, node, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `cs` may be interpreted in a read as the target of some store - * into `c`, in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCandSet(ContentSet cs, Content c, Configuration config) { - fwdFlowConsCand(c, config) and - c = cs.getAReadContent() - } - - pragma[nomagic] - private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { - exists(RetNodeEx ret | - fwdFlow(ret, cc, config) and - ret.getReturnPosition() = pos - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) { - exists(ReturnPosition pos | - fwdFlowReturnPosition(pos, cc, config) and - viableReturnPosOutEx(call, pos, out) and - not fullBarrier(out, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) { - fwdFlowOut(call, out, true, config) - } - - private predicate stateStepFwd(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1 | - additionalLocalStateStep(node1, state1, _, state2, config) or - additionalJumpStateStep(node1, state1, _, state2, config) - | - fwdFlow(node1, config) - ) - } - - private predicate fwdFlowState(FlowState state, Configuration config) { - sourceNode(_, state, config) - or - exists(FlowState state0 | - fwdFlowState(state0, config) and - stateStepFwd(state0, state, config) - ) - } - - /** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink. - */ - pragma[nomagic] - private predicate revFlow(NodeEx node, boolean toReturn, Configuration config) { - revFlow0(node, toReturn, config) and - fwdFlow(node, config) - } - - pragma[nomagic] - private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) { - exists(FlowState state | - fwdFlow(node, pragma[only_bind_into](config)) and - sinkNode(node, state, config) and - fwdFlowState(state, pragma[only_bind_into](config)) and - if hasSinkCallCtx(config) then toReturn = true else toReturn = false - ) - or - exists(NodeEx mid | revFlow(mid, toReturn, config) | - localFlowStep(node, mid, config) or - additionalLocalFlowStep(node, mid, config) or - additionalLocalStateStep(node, _, mid, _, config) - ) - or - exists(NodeEx mid | revFlow(mid, _, config) and toReturn = false | - jumpStep(node, mid, config) or - additionalJumpStep(node, mid, config) or - additionalJumpStateStep(node, _, mid, _, config) - ) - or - // store - exists(Content c | - revFlowStore(c, node, toReturn, config) and - revFlowConsCand(c, config) - ) - or - // read - exists(NodeEx mid, ContentSet c | - readSet(node, c, mid, config) and - fwdFlowConsCandSet(c, _, pragma[only_bind_into](config)) and - revFlow(mid, toReturn, pragma[only_bind_into](config)) - ) - or - // flow into a callable - revFlowIn(_, node, false, config) and - toReturn = false - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(pos, config) and - node.(RetNodeEx).getReturnPosition() = pos and - toReturn = true - ) - or - // flow through a callable - exists(DataFlowCall call | - revFlowInToReturn(call, node, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - /** - * Holds if `c` is the target of a read in the flow covered by `revFlow`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, ContentSet cs | - fwdFlow(node, pragma[only_bind_into](config)) and - readSet(node, cs, mid, config) and - fwdFlowConsCandSet(cs, c, pragma[only_bind_into](config)) and - revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) { - exists(NodeEx mid, TypedContent tc | - revFlow(mid, toReturn, pragma[only_bind_into](config)) and - fwdFlowConsCand(c, pragma[only_bind_into](config)) and - store(node, tc, mid, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `c` is the target of both a read and a store in the flow covered - * by `revFlow`. - */ - pragma[nomagic] - additional predicate revFlowIsReadAndStored(Content c, Configuration conf) { - revFlowConsCand(c, conf) and - revFlowStore(c, _, _, conf) - } - - pragma[nomagic] - additional predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config - ) { - fwdFlowReturnPosition(pos, _, config) and - viableReturnPosOutEx(call, pos, out) - } - - pragma[nomagic] - private predicate revFlowOut(ReturnPosition pos, Configuration config) { - exists(NodeEx out | - revFlow(out, _, config) and - viableReturnPosOutNodeCandFwd1(_, pos, out, config) - ) - } - - pragma[nomagic] - additional predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config - ) { - fwdFlowIn(call, arg, _, p, config) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate revFlowIn( - DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config - ) { - exists(ParamNodeEx p | - revFlow(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) { - revFlowIn(call, arg, true, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(NodeEx out | - revFlow(out, toReturn, config) and - fwdFlowOutFromArg(call, out, config) - ) - } - - private predicate stateStepRev(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - additionalLocalStateStep(node1, state1, node2, state2, config) or - additionalJumpStateStep(node1, state1, node2, state2, config) - | - revFlow(node1, _, pragma[only_bind_into](config)) and - revFlow(node2, _, pragma[only_bind_into](config)) and - fwdFlowState(state1, pragma[only_bind_into](config)) and - fwdFlowState(state2, pragma[only_bind_into](config)) - ) - } - - additional predicate revFlowState(FlowState state, Configuration config) { - exists(NodeEx node | - sinkNode(node, state, config) and - revFlow(node, _, pragma[only_bind_into](config)) and - fwdFlowState(state, pragma[only_bind_into](config)) - ) - or - exists(FlowState state0 | - revFlowState(state0, config) and - stateStepRev(state, state0, config) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Content c | - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - revFlow(node2, pragma[only_bind_into](config)) and - store(node1, tc, node2, contentType, config) and - c = tc.getContent() and - exists(ap1) - ) - } - - pragma[nomagic] - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) { - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - read(n1, c, n2, pragma[only_bind_into](config)) and - revFlow(n2, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, config) and - exists(ap) - } - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, _, pragma[only_bind_into](config)) and - exists(state) and - exists(ap) - } - - private predicate throughFlowNodeCand(NodeEx node, Configuration config) { - revFlow(node, true, config) and - fwdFlow(node, true, config) and - not inBarrier(node, config) and - not outBarrier(node, config) - } - - /** Holds if flow may return from `callable`. */ - pragma[nomagic] - private predicate returnFlowCallableNodeCand( - DataFlowCallable callable, ReturnKindExt kind, Configuration config - ) { - exists(RetNodeEx ret | - throughFlowNodeCand(ret, config) and - callable = ret.getEnclosingCallable() and - kind = ret.getKind() - ) - } - - /** - * Holds if flow may enter through `p` and reach a return node making `p` a - * candidate for the origin of a summary. - */ - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(DataFlowCallable c, ReturnKindExt kind | - throughFlowNodeCand(p, config) and - returnFlowCallableNodeCand(c, kind, config) and - p.getEnclosingCallable() = c and - exists(ap) and - parameterFlowThroughAllowed(p, kind) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - throughFlowNodeCand(ret, config) and - kind = ret.getKind() and - exists(argAp) and - exists(ap) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, boolean toReturn | - revFlow(arg, toReturn, config) and - revFlowInToReturn(call, arg, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, config)) and - fields = count(Content f0 | fwdFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | fwdFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, config)) and - fields = count(Content f0 | revFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | revFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | revFlow(n, b, config)) - } - /* End: Stage 1 logic. */ -} - -pragma[noinline] -private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - localFlowStep(node1, node2, config) -} - -pragma[noinline] -private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - additionalLocalFlowStep(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCand1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config -) { - Stage1::revFlow(out, config) and - Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, Configuration config -) { - exists(ReturnPosition pos | - viableReturnPosOutNodeCand1(call, pos, out, config) and - pos = ret.getReturnPosition() and - kind = pos.getKind() and - Stage1::revFlow(ret, config) and - not outBarrier(ret, config) and - not inBarrier(out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCand1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config -) { - Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and - Stage1::revFlow(arg, config) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config -) { - viableParamArgNodeCand1(call, p, arg, config) and - Stage1::revFlow(p, config) and - not outBarrier(arg, config) and - not inBarrier(p, config) -} - -/** - * Gets the amount of forward branching on the origin of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int branch(NodeEx n1, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n1, _, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) - ) -} - -/** - * Gets the amount of backward branching on the target of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int join(NodeEx n2, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n, _, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) - ) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. The - * `allowsFieldFlow` flag indicates whether the branching is within the limit - * specified by the configuration. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, ret, kind, out, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(ret, pragma[only_bind_into](config)) and - j = join(out, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. The `allowsFieldFlow` flag indicates whether - * the branching is within the limit specified by the configuration. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config -) { - flowIntoCallNodeCand1(call, arg, p, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(arg, pragma[only_bind_into](config)) and - j = join(p, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -private signature module StageSig { - class Ap; - - predicate revFlow(NodeEx node, Configuration config); - - predicate revFlowAp(NodeEx node, Ap ap, Configuration config); - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config); - - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config); - - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config); - - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ); - - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ); - - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config); -} - -private module MkStage { - class ApApprox = PrevStage::Ap; - - signature module StageParam { - class Ap; - - class ApNil extends Ap; - - bindingset[result, ap] - ApApprox getApprox(Ap ap); - - ApNil getApNil(NodeEx node); - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail); - - /** - * An approximation of `Content` that corresponds to the precision level of - * `Ap`, such that the mappings from both `Ap` and `Content` to this type - * are functional. - */ - class ApHeadContent; - - ApHeadContent getHeadContent(Ap ap); - - ApHeadContent projectToHeadContent(Content c); - - class ApOption; - - ApOption apNone(); - - ApOption apSome(Ap ap); - - class Cc; - - class CcCall extends Cc; - - // TODO: member predicate on CcCall - predicate matchesCall(CcCall cc, DataFlowCall call); - - class CcNoCall extends Cc; - - Cc ccNone(); - - CcCall ccSomeCall(); - - class LocalCc; - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc); - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ); - - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config - ); - - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config - ); - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config); - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType); - } - - module Stage implements StageSig { - import Param - - /* Begin: Stage logic. */ - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - private predicate revFlowApAlias(NodeEx node, ApApprox apa, Configuration config) { - PrevStage::revFlowAp(node, apa, config) - } - - pragma[nomagic] - private predicate flowIntoCallApa( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa, - Configuration config - ) { - flowIntoCall(call, arg, p, allowsFieldFlow, config) and - PrevStage::revFlowAp(p, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(arg, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowOutOfCallApa( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - ApApprox apa, Configuration config - ) { - flowOutOfCall(call, ret, kind, out, allowsFieldFlow, config) and - PrevStage::revFlowAp(out, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(ret, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowThroughOutOfCall( - DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, - ApApprox argApa, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa, pragma[only_bind_into](config)) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) and - matchesCall(ccc, call) - ) - } - - /** - * Holds if `node` is reachable with access path `ap` from a source in the - * configuration `config`. - * - * The call context `cc` records whether the node is reached through an - * argument in a call, and if so, `summaryCtx` and `argAp` record the - * corresponding parameter position and access path of that argument, respectively. - */ - pragma[nomagic] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa, config) and - PrevStage::revFlow(node, state, apa, config) and - filter(node, state, ap, config) - } - - pragma[inline] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, cc, summaryCtx, argAp, ap, _, config) - } - - pragma[nomagic] - private predicate fwdFlow0( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - sourceNode(node, state, config) and - (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and - argAp = apNone() and - summaryCtx = TParamNodeNone() and - ap = getApNil(node) and - apa = getApprox(ap) - or - exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | - fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0, config) and - localCc = getLocalCc(mid, cc) - | - localStep(mid, state0, node, state, true, _, config, localCc) and - ap = ap0 and - apa = apa0 - or - localStep(mid, state0, node, state, false, ap, config, localCc) and - ap0 instanceof ApNil and - apa = getApprox(ap) - ) - or - exists(NodeEx mid | - fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa, pragma[only_bind_into](config)) and - jumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(mid, state, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(mid, state0, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStateStep(mid, state0, node, state, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - // store - exists(TypedContent tc, Ap ap0 | - fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp, config) and - ap = apCons(tc, ap0) and - apa = getApprox(ap) - ) - or - // read - exists(Ap ap0, Content c | - fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp, config) and - fwdFlowConsCand(ap0, c, ap, config) and - apa = getApprox(ap) - ) - or - // flow into a callable - fwdFlowIn(_, node, state, _, cc, _, _, ap, apa, config) and - if PrevStage::parameterMayFlowThrough(node, apa, config) - then ( - summaryCtx = TParamNodeSome(node.asNode()) and - argAp = apSome(ap) - ) else ( - summaryCtx = TParamNodeNone() and argAp = apNone() - ) - or - // flow out of a callable - exists( - DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, - DataFlowCallable inner - | - fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa, config) and - flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa, config) and - inner = ret.getEnclosingCallable() and - cc = getCallContextReturn(inner, call, innercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - or - // flow through a callable - exists( - DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, ApApprox innerArgApa - | - fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowStore( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(DataFlowType contentType, ApApprox apa1 | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1, config) and - PrevStage::storeStepCand(node1, apa1, tc, node2, contentType, config) and - typecheckStore(ap1, contentType) - ) - } - - /** - * Holds if forward flow with access path `tail` reaches a store of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(TypedContent tc | - fwdFlowStore(_, tail, tc, _, _, _, _, _, config) and - tc.getContent() = c and - cons = apCons(tc, tail) - ) - } - - pragma[nomagic] - private predicate readStepCand( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - PrevStage::readStepCand(node1, c, node2, config) and - apc = projectToHeadContent(c) - } - - bindingset[node1, apc] - pragma[inline_late] - private predicate readStepCand0( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - readStepCand(node1, apc, c, node2, config) - } - - pragma[nomagic] - private predicate fwdFlowRead( - Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(ApHeadContent apc | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap, config) and - apc = getHeadContent(ap) and - readStepCand0(node1, apc, c, node2, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowIn( - DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, - ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa, Configuration config - ) { - exists(ArgNodeEx arg, boolean allowsFieldFlow | - fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa, config) and - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowRetFromArg( - RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, ApApprox argApa, - Ap ap, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - fwdFlow(pragma[only_bind_into](ret), state, ccc, - TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), - pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa), - pragma[only_bind_into](config)) and - kind = ret.getKind() and - parameterFlowThroughAllowed(summaryCtx, kind) and - argApa = getApprox(argAp) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) - ) - } - - pragma[inline] - private predicate fwdFlowThrough0( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, - Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa, config) and - fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp, config) - } - - pragma[nomagic] - private predicate fwdFlowThrough( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa, - config) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` - * and data might flow through the target callable and back out at `call`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered( - DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, - ParamNodeEx p, Ap ap, Configuration config - ) { - exists(ApApprox apa | - fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(p, apa, config) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate storeStepFwd( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config - ) { - fwdFlowStore(node1, ap1, tc, node2, _, _, _, _, config) and - ap2 = apCons(tc, ap1) and - fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _, config) - } - - private predicate readStepFwd( - NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config - ) { - fwdFlowRead(ap1, c, n1, n2, _, _, _, _, config) and - fwdFlowConsCand(ap1, c, ap2, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough0( - DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, - ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, - innerArgApa, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough( - RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, - Ap ap, Configuration config - ) { - exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | - returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa, config) and - pos = ret.getReturnPosition() and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowThroughIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap, - Configuration config - ) { - exists(ApApprox argApa | - flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), - allowsFieldFlow, argApa, pragma[only_bind_into](config)) and - fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa, - pragma[only_bind_into](config)) and - returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap, - pragma[only_bind_into](config)) and - if allowsFieldFlow = false then argAp instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowIntoCallAp( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap, - Configuration config - ) { - exists(ApApprox apa | - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - fwdFlow(arg, _, _, _, _, ap, apa, config) - ) - } - - pragma[nomagic] - private predicate flowOutOfCallAp( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, - Ap ap, Configuration config - ) { - exists(ApApprox apa | - flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa, config) and - fwdFlow(ret, _, _, _, _, ap, apa, config) and - pos = ret.getReturnPosition() - ) - } - - /** - * Holds if `node` with access path `ap` is part of a path from a source to a - * sink in the configuration `config`. - * - * The parameter `returnCtx` records whether (and how) the node must be returned - * from the enclosing callable in order to reach a sink, and if so, `returnAp` - * records the access path of the returned value. - */ - pragma[nomagic] - additional predicate revFlow( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - revFlow0(node, state, returnCtx, returnAp, ap, config) and - fwdFlow(node, state, _, _, _, ap, config) - } - - pragma[nomagic] - private predicate revFlow0( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, _, _, _, ap, config) and - sinkNode(node, state, config) and - ( - if hasSinkCallCtx(config) - then returnCtx = TReturnCtxNoFlowThrough() - else returnCtx = TReturnCtxNone() - ) and - returnAp = apNone() and - ap instanceof ApNil - or - exists(NodeEx mid, FlowState state0 | - localStep(node, state, mid, state0, true, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, ap, config) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap, pragma[only_bind_into](config)) and - localStep(node, pragma[only_bind_into](state), mid, state0, false, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, nil, pragma[only_bind_into](config)) and - ap instanceof ApNil - ) - or - exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, state, _, _, ap, config) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStep(node, mid, config) and - revFlow(pragma[only_bind_into](mid), state, _, _, nil, pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStateStep(node, state, mid, state0, config) and - revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil, - pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - // store - exists(Ap ap0, Content c | - revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp, config) and - revFlowConsCand(ap0, c, ap, config) - ) - or - // read - exists(NodeEx mid, Ap ap0 | - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - readStepFwd(node, ap, _, mid, ap0, config) - ) - or - // flow into a callable - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, state, TReturnCtxNone(), returnAp, ap, config) and - flowIntoCallAp(_, node, p, allowsFieldFlow, ap, config) and - (if allowsFieldFlow = false then ap instanceof ApNil else any()) and - returnCtx = TReturnCtxNone() - ) - or - // flow through a callable - exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, node, p, _, ap, innerReturnAp, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(_, node, pos, state, _, _, ap, config) and - if returnFlowsThrough(node, pos, state, _, _, _, ap, config) - then ( - returnCtx = TReturnCtxMaybeFlowThrough(pos) and - returnAp = apSome(ap) - ) else ( - returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() - ) - ) - } - - pragma[nomagic] - private predicate revFlowStore( - Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, - ReturnCtx returnCtx, ApOption returnAp, Configuration config - ) { - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - storeStepFwd(node, ap, tc, mid, ap0, config) and - tc.getContent() = c - } - - /** - * Holds if reverse flow with access path `tail` reaches a read of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(NodeEx mid, Ap tail0 | - revFlow(mid, _, _, _, tail, config) and - tail = pragma[only_bind_into](tail0) and - readStepFwd(_, cons, c, mid, tail0, config) - ) - } - - pragma[nomagic] - private predicate revFlowOut( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, - ApOption returnAp, Ap ap, Configuration config - ) { - exists(NodeEx out, boolean allowsFieldFlow | - revFlow(out, state, returnCtx, returnAp, ap, config) and - flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate revFlowParamToReturn( - ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap, Configuration config - ) { - revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), - pragma[only_bind_into](ap), pragma[only_bind_into](config)) and - parameterFlowThroughAllowed(p, pos.getKind()) and - PrevStage::parameterMayFlowThrough(p, getApprox(ap), config) - } - - pragma[nomagic] - private predicate revFlowThrough( - DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, - ApOption returnAp, Ap ap, Ap innerReturnAp, Configuration config - ) { - revFlowParamToReturn(p, state, pos, innerReturnAp, ap, config) and - revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned( - DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap, - Configuration config - ) { - exists(RetNodeEx ret, FlowState state, CcCall ccc | - revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap, config) and - returnFlowsThrough(ret, pos, state, ccc, _, _, ap, config) and - matchesCall(ccc, call) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Ap ap2, Content c | - PrevStage::storeStepCand(node1, _, tc, node2, contentType, config) and - revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _, config) and - revFlowConsCand(ap2, c, ap1, config) - ) - } - - predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(Ap ap1, Ap ap2 | - revFlow(node2, _, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and - readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _, - pragma[only_bind_into](config)) - ) - } - - additional predicate revFlow(NodeEx node, FlowState state, Configuration config) { - revFlow(node, state, _, _, _, config) - } - - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, _, _, ap, config) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, _, _, _, ap, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, Configuration config) { - revFlow(node, _, _, _, _, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, ap, config) - } - - private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepFwd(_, ap, tc, _, _, config) - } - - private predicate revConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepCand(_, ap, tc, _, _, config) - } - - private predicate validAp(Ap ap, Configuration config) { - revFlow(_, _, _, _, ap, config) and ap instanceof ApNil - or - exists(TypedContent head, Ap tail | - consCand(head, tail, config) and - ap = apCons(head, tail) - ) - } - - additional predicate consCand(TypedContent tc, Ap ap, Configuration config) { - revConsCand(tc, ap, config) and - validAp(ap, config) - } - - pragma[nomagic] - private predicate parameterFlowsThroughRev( - ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp, Configuration config - ) { - revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap, config) and - parameterFlowThroughAllowed(p, pos.getKind()) - } - - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(ReturnPosition pos | - returnFlowsThrough(_, pos, _, _, p, ap, _, config) and - parameterFlowsThroughRev(p, ap, pos, _, config) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - exists(ParamNodeEx p, ReturnPosition pos | - returnFlowsThrough(ret, pos, _, _, p, argAp, ap, config) and - parameterFlowsThroughRev(p, argAp, pos, ap, config) and - kind = pos.getKind() - ) - } - - pragma[nomagic] - private predicate revFlowThroughArg( - DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, - Ap ap, Configuration config - ) { - exists(ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp, config) - ) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | - revFlow(arg, state, returnCtx, returnAp, ap, config) and - revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, config)) and - fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and - states = count(FlowState state | fwdFlow(_, state, _, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | - fwdFlow(n, state, cc, summaryCtx, argAp, ap, config) - ) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, _, _, _, config)) and - fields = count(TypedContent f0 | consCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and - states = count(FlowState state | revFlow(_, state, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | - revFlow(n, state, returnCtx, retAp, ap, config) - ) - } - /* End: Stage logic. */ - } -} - -private module BooleanCallContext { - class Cc extends boolean { - Cc() { this in [true, false] } - } - - class CcCall extends Cc { - CcCall() { this = true } - } - - /** Holds if the call context may be `call`. */ - predicate matchesCall(CcCall cc, DataFlowCall call) { any() } - - class CcNoCall extends Cc { - CcNoCall() { this = false } - } - - Cc ccNone() { result = false } - - CcCall ccSomeCall() { result = true } - - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } -} - -private module Level1CallContext { - class Cc = CallContext; - - class CcCall = CallContextCall; - - pragma[inline] - predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } - - class CcNoCall = CallContextNoCall; - - Cc ccNone() { result instanceof CallContextAny } - - CcCall ccSomeCall() { result instanceof CallContextSomeCall } - - module NoLocalCallContext { - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSiteDispatch(call, c) - then result = TSpecificCall(call) - else result = TSomeCall() - } - } - - module LocalCallContext { - class LocalCc = LocalCallContext; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { - result = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - node.getEnclosingCallable()) - } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() - } - } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { - checkCallContextReturn(innercc, c, call) and - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() - } -} - -private module Stage2Param implements MkStage::StageParam { - private module PrevStage = Stage1; - - class Ap extends boolean { - Ap() { this in [true, false] } - } - - class ApNil extends Ap { - ApNil() { this = false } - } - - bindingset[result, ap] - PrevStage::Ap getApprox(Ap ap) { any() } - - ApNil getApNil(NodeEx node) { Stage1::revFlow(node, _) and exists(result) } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } - - class ApHeadContent = Unit; - - pragma[inline] - ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } - - ApHeadContent projectToHeadContent(Content c) { any() } - - class ApOption = BooleanOption; - - ApOption apNone() { result = TBooleanNone() } - - ApOption apSome(Ap ap) { result = TBooleanSome(ap) } - - import Level1CallContext - import NoLocalCallContext - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - ( - preservesValue = true and - localFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalStateStep(node1, state1, node2, state2, config) - ) and - exists(ap) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand1/6; - - predicate flowIntoCall = flowIntoCallNodeCand1/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - expectsContentEx(node, c) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - PrevStage::revFlowState(state, pragma[only_bind_into](config)) and - exists(ap) and - not stateBarrier(node, state, config) and - ( - notExpectsContent(node) - or - ap = true and - expectsContentCand(node, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage2 implements StageSig { - import MkStage::Stage -} - -pragma[nomagic] -private predicate flowOutOfCallNodeCand2( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -pragma[nomagic] -private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -private module LocalFlowBigStep { - /** - * A node where some checking is required, and hence the big-step relation - * is not allowed to step over. - */ - private class FlowCheckNode extends NodeEx { - FlowCheckNode() { - castNode(this.asNode()) or - clearsContentCached(this.asNode(), _) or - expectsContentCached(this.asNode(), _) - } - } - - /** - * Holds if `node` can be the first node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowEntry(NodeEx node, FlowState state, Configuration config) { - Stage2::revFlow(node, state, config) and - ( - sourceNode(node, state, config) - or - jumpStep(_, node, config) - or - additionalJumpStep(_, node, config) - or - additionalJumpStateStep(_, _, node, state, config) - or - node instanceof ParamNodeEx - or - node.asNode() instanceof OutNodeExt - or - Stage2::storeStepCand(_, _, _, node, _, config) - or - Stage2::readStepCand(_, _, node, config) - or - node instanceof FlowCheckNode - or - exists(FlowState s | - additionalLocalStateStep(_, s, node, state, config) and - s != state - ) - ) - } - - /** - * Holds if `node` can be the last node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowExit(NodeEx node, FlowState state, Configuration config) { - exists(NodeEx next | Stage2::revFlow(next, state, config) | - jumpStep(node, next, config) or - additionalJumpStep(node, next, config) or - flowIntoCallNodeCand2(_, node, next, _, config) or - flowOutOfCallNodeCand2(_, node, _, next, _, config) or - Stage2::storeStepCand(node, _, _, next, _, config) or - Stage2::readStepCand(node, _, next, config) - ) - or - exists(NodeEx next, FlowState s | Stage2::revFlow(next, s, config) | - additionalJumpStateStep(node, state, next, s, config) - or - additionalLocalStateStep(node, state, next, s, config) and - s != state - ) - or - Stage2::revFlow(node, state, config) and - node instanceof FlowCheckNode - or - sinkNode(node, state, config) - } - - pragma[noinline] - private predicate additionalLocalFlowStepNodeCand2( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, Configuration config - ) { - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 and - Stage2::revFlow(node1, pragma[only_bind_into](state1), false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false, - pragma[only_bind_into](config)) - or - additionalLocalStateStep(node1, state1, node2, state2, config) and - Stage2::revFlow(node1, state1, false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, state2, false, pragma[only_bind_into](config)) - } - - /** - * Holds if the local path from `node1` to `node2` is a prefix of a maximal - * subsequence of local flow steps in a dataflow path. - * - * This is the transitive closure of `[additional]localFlowStep` beginning - * at `localFlowEntry`. - */ - pragma[nomagic] - private predicate localFlowStepPlus( - NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, - Configuration config, LocalCallContext cc - ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and - ( - localFlowEntry(node1, pragma[only_bind_into](state), pragma[only_bind_into](config)) and - ( - localFlowStepNodeCand1(node1, node2, config) and - preservesValue = true and - t = node1.getDataFlowType() and // irrelevant dummy value - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - or - additionalLocalFlowStepNodeCand2(node1, state, node2, state, config) and - preservesValue = false and - t = node2.getDataFlowType() - ) and - node1 != node2 and - cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) - or - exists(NodeEx mid | - localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, - pragma[only_bind_into](config), cc) and - localFlowStepNodeCand1(mid, node2, config) and - not mid instanceof FlowCheckNode and - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - ) - or - exists(NodeEx mid | - localFlowStepPlus(node1, state, mid, _, _, pragma[only_bind_into](config), cc) and - additionalLocalFlowStepNodeCand2(mid, state, node2, state, config) and - not mid instanceof FlowCheckNode and - preservesValue = false and - t = node2.getDataFlowType() - ) - ) - } - - /** - * Holds if `node1` can step to `node2` in one or more local steps and this - * path can occur as a maximal subsequence of local steps in a dataflow path. - */ - pragma[nomagic] - predicate localFlowBigStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - DataFlowType t, Configuration config, LocalCallContext callContext - ) { - localFlowStepPlus(node1, state1, node2, preservesValue, t, config, callContext) and - localFlowExit(node2, state1, config) and - state1 = state2 - or - additionalLocalFlowStepNodeCand2(node1, state1, node2, state2, config) and - state1 != state2 and - preservesValue = false and - t = node2.getDataFlowType() and - callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) - } -} - -private import LocalFlowBigStep - -private module Stage3Param implements MkStage::StageParam { - private module PrevStage = Stage2; - - class Ap = ApproxAccessPathFront; - - class ApNil = ApproxAccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } - - class ApHeadContent = ContentApprox; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - predicate projectToHeadContent = getContentApprox/1; - - class ApOption = ApproxAccessPathFrontOption; - - ApOption apNone() { result = TApproxAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } - - import BooleanCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApproxAccessPathFrontNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand2/6; - - predicate flowIntoCall = flowIntoCallNodeCand2/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getAHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage3 implements StageSig { - import MkStage::Stage -} - -private module Stage4Param implements MkStage::StageParam { - private module PrevStage = Stage3; - - class Ap = AccessPathFront; - - class ApNil = AccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathFrontOption; - - ApOption apNone() { result = TAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } - - import BooleanCallContext - - pragma[nomagic] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) and - exists(lcc) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clearSet(NodeEx node, ContentSet c, Configuration config) { - PrevStage::revFlow(node, config) and - clearsContentCached(node.asNode(), c) - } - - pragma[nomagic] - private predicate clearContent(NodeEx node, Content c, Configuration config) { - exists(ContentSet cs | - PrevStage::readStepCand(_, pragma[only_bind_into](c), _, pragma[only_bind_into](config)) and - c = cs.getAReadContent() and - clearSet(node, cs, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clear(NodeEx node, Ap ap, Configuration config) { - clearContent(node, ap.getHead().getContent(), config) - } - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - not clear(node, ap, config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage4 implements StageSig { - import MkStage::Stage -} - -/** - * Holds if `argApf` is recorded as the summary context for flow reaching `node` - * and remains relevant for the following pruning stage. - */ -private predicate flowCandSummaryCtx( - NodeEx node, FlowState state, AccessPathFront argApf, Configuration config -) { - exists(AccessPathFront apf | - Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf, config) and - Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf, - config) - ) -} - -/** - * Holds if a length 2 access path approximation with the head `tc` is expected - * to be expensive. - */ -private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { - exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf, config)) and - nodes = - strictcount(NodeEx n, FlowState state | - Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - or - flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - ) and - accessPathApproxCostLimits(apLimit, tupleLimit) and - apLimit < tails and - tupleLimit < (tails - 1) * nodes and - not tc.forceHighPrecision() - ) -} - -private newtype TAccessPathApprox = - TNil(DataFlowType t) or - TConsNil(TypedContent tc, DataFlowType t) { - Stage4::consCand(tc, TFrontNil(t), _) and - not expensiveLen2unfolding(tc, _) - } or - TConsCons(TypedContent tc1, TypedContent tc2, int len) { - Stage4::consCand(tc1, TFrontHead(tc2), _) and - len in [2 .. accessPathLimit()] and - not expensiveLen2unfolding(tc1, _) - } or - TCons1(TypedContent tc, int len) { - len in [1 .. accessPathLimit()] and - expensiveLen2unfolding(tc, _) - } - -/** - * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only - * the first two elements of the list and its length are tracked. If data flows - * from a source to a given node with a given `AccessPathApprox`, this indicates - * the sequence of dereference operations needed to get from the value in the node - * to the tracked object. The final type indicates the type of the tracked object. - */ -abstract private class AccessPathApprox extends TAccessPathApprox { - abstract string toString(); - - abstract TypedContent getHead(); - - abstract int len(); - - abstract DataFlowType getType(); - - abstract AccessPathFront getFront(); - - /** Gets the access path obtained by popping `head` from this path, if any. */ - abstract AccessPathApprox pop(TypedContent head); -} - -private class AccessPathApproxNil extends AccessPathApprox, TNil { - private DataFlowType t; - - AccessPathApproxNil() { this = TNil(t) } - - override string toString() { result = concat(": " + ppReprType(t)) } - - override TypedContent getHead() { none() } - - override int len() { result = 0 } - - override DataFlowType getType() { result = t } - - override AccessPathFront getFront() { result = TFrontNil(t) } - - override AccessPathApprox pop(TypedContent head) { none() } -} - -abstract private class AccessPathApproxCons extends AccessPathApprox { } - -private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { - private TypedContent tc; - private DataFlowType t; - - AccessPathApproxConsNil() { this = TConsNil(tc, t) } - - override string toString() { - // The `concat` becomes "" if `ppReprType` has no result. - result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) - } - - override TypedContent getHead() { result = tc } - - override int len() { result = 1 } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } -} - -private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { - private TypedContent tc1; - private TypedContent tc2; - private int len; - - AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - - override string toString() { - if len = 2 - then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" - else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc1 } - - override int len() { result = len } - - override DataFlowType getType() { result = tc1.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc1) } - - override AccessPathApprox pop(TypedContent head) { - head = tc1 and - ( - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - } -} - -private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { - private TypedContent tc; - private int len; - - AccessPathApproxCons1() { this = TCons1(tc, len) } - - override string toString() { - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc } - - override int len() { result = len } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { - head = tc and - ( - exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2), _) | - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - or - exists(DataFlowType t | - len = 1 and - Stage4::consCand(tc, TFrontNil(t), _) and - result = TNil(t) - ) - ) - } -} - -/** Gets the access path obtained by popping `tc` from `ap`, if any. */ -private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } - -/** Gets the access path obtained by pushing `tc` onto `ap`. */ -private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } - -private newtype TAccessPathApproxOption = - TAccessPathApproxNone() or - TAccessPathApproxSome(AccessPathApprox apa) - -private class AccessPathApproxOption extends TAccessPathApproxOption { - string toString() { - this = TAccessPathApproxNone() and result = "" - or - this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) - } -} - -private module Stage5Param implements MkStage::StageParam { - private module PrevStage = Stage4; - - class Ap = AccessPathApprox; - - class ApNil = AccessPathApproxNil; - - pragma[nomagic] - PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathApproxOption; - - ApOption apNone() { result = TAccessPathApproxNone() } - - ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } - - import Level1CallContext - import LocalCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, lcc) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { any() } - - // Type checking is not necessary here as it has already been done in stage 3. - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage5 = MkStage::Stage; - -bindingset[conf, result] -private Configuration unbindConf(Configuration conf) { - exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c)) -} - -pragma[nomagic] -private predicate nodeMayUseSummary0( - NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(AccessPathApprox apa0 | - Stage5::parameterMayFlowThrough(p, _, _) and - Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0, config) and - Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), - TAccessPathApproxSome(apa), apa0, config) - ) -} - -pragma[nomagic] -private predicate nodeMayUseSummary( - NodeEx n, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::parameterMayFlowThrough(p, apa, config) and - nodeMayUseSummary0(n, p, state, apa, config) - ) -} - -private newtype TSummaryCtx = - TSummaryCtxNone() or - TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { - exists(Configuration config | - Stage5::parameterMayFlowThrough(p, ap.getApprox(), config) and - Stage5::revFlow(p, state, _, config) - ) - } - -/** - * A context for generating flow summaries. This represents flow entry through - * a specific parameter with an access path of a specific shape. - * - * Summaries are only created for parameters that may flow through. - */ -abstract private class SummaryCtx extends TSummaryCtx { - abstract string toString(); -} - -/** A summary context from which no flow summary can be generated. */ -private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { - override string toString() { result = "" } -} - -/** A summary context from which a flow summary can be generated. */ -private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParamNodeEx p; - private FlowState s; - private AccessPath ap; - - SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } - - ParameterPosition getParameterPos() { p.isParameterOf(_, result) } - - ParamNodeEx getParamNode() { result = p } - - override string toString() { result = p + ": " + ap } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** - * Gets the number of length 2 access path approximations that correspond to `apa`. - */ -private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { - exists(TypedContent tc, int len | - tc = apa.getHead() and - len = apa.len() and - result = - strictcount(AccessPathFront apf | - Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), - config) - ) - ) -} - -private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = - strictcount(NodeEx n, FlowState state | - Stage5::revFlow(n, state, apa, config) or nodeMayUseSummary(n, state, apa, config) - ) -} - -/** - * Holds if a length 2 access path approximation matching `apa` is expected - * to be expensive. - */ -private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = count1to2unfold(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - apLimit < aps and - tupleLimit < (aps - 1) * nodes - ) -} - -private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { - exists(TypedContent head | - apa.pop(head) = result and - Stage5::consCand(head, result, config) - ) -} - -/** - * Holds with `unfold = false` if a precise head-tail representation of `apa` is - * expected to be expensive. Holds with `unfold = true` otherwise. - */ -private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { - if apa.getHead().forceHighPrecision() - then unfold = true - else - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = countPotentialAps(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true - ) -} - -/** - * Gets the number of `AccessPath`s that correspond to `apa`. - */ -pragma[assume_small_delta] -private int countAps(AccessPathApprox apa, Configuration config) { - evalUnfold(apa, false, config) and - result = 1 and - (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) - or - evalUnfold(apa, false, config) and - result = count1to2unfold(apa, config) and - not expensiveLen1to2unfolding(apa, config) - or - evalUnfold(apa, true, config) and - result = countPotentialAps(apa, config) -} - -/** - * Gets the number of `AccessPath`s that would correspond to `apa` assuming - * that it is expanded to a precise head-tail representation. - */ -language[monotonicAggregates] -pragma[assume_small_delta] -private int countPotentialAps(AccessPathApprox apa, Configuration config) { - apa instanceof AccessPathApproxNil and result = 1 - or - result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) -} - -private newtype TAccessPath = - TAccessPathNil(DataFlowType t) or - TAccessPathCons(TypedContent head, AccessPath tail) { - exists(AccessPathApproxCons apa | - not evalUnfold(apa, false, _) and - head = apa.getHead() and - tail.getApprox() = getATail(apa, _) - ) - } or - TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - not expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head1 = apa.getHead() and - head2 = getATail(apa, _).getHead() - ) - } or - TAccessPathCons1(TypedContent head, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head = apa.getHead() - ) - } - -private newtype TPathNode = - pragma[assume_small_delta] - TPathNodeMid( - NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config - ) { - // A PathNode is introduced by a source ... - Stage5::revFlow(node, state, config) and - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - or - // ... or a step from an existing PathNode to another node. - exists(PathNodeMid mid | - pathStep(mid, node, state, cc, sc, ap) and - pragma[only_bind_into](config) = mid.getConfiguration() and - Stage5::revFlow(node, state, ap.getApprox(), pragma[only_bind_into](config)) - ) - } or - TPathNodeSink(NodeEx node, FlowState state, Configuration config) { - exists(PathNodeMid sink | - sink.isAtSink() and - node = sink.getNodeEx() and - state = sink.getState() and - config = sink.getConfiguration() - ) - } or - TPathNodeSourceGroup(string sourceGroup, Configuration config) { - exists(PathNodeImpl source | - sourceGroup = source.getSourceGroup() and - config = source.getConfiguration() - ) - } or - TPathNodeSinkGroup(string sinkGroup, Configuration config) { - exists(PathNodeSink sink | - sinkGroup = sink.getSinkGroup() and - config = sink.getConfiguration() - ) - } - -/** - * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a - * source to a given node with a given `AccessPath`, this indicates the sequence - * of dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ -private class AccessPath extends TAccessPath { - /** Gets the head of this access path, if any. */ - abstract TypedContent getHead(); - - /** Gets the tail of this access path, if any. */ - abstract AccessPath getTail(); - - /** Gets the front of this access path. */ - abstract AccessPathFront getFront(); - - /** Gets the approximation of this access path. */ - abstract AccessPathApprox getApprox(); - - /** Gets the length of this access path. */ - abstract int length(); - - /** Gets a textual representation of this access path. */ - abstract string toString(); - - /** Gets the access path obtained by popping `tc` from this access path, if any. */ - final AccessPath pop(TypedContent tc) { - result = this.getTail() and - tc = this.getHead() - } - - /** Gets the access path obtained by pushing `tc` onto this access path. */ - final AccessPath push(TypedContent tc) { this = result.pop(tc) } -} - -private class AccessPathNil extends AccessPath, TAccessPathNil { - private DataFlowType t; - - AccessPathNil() { this = TAccessPathNil(t) } - - DataFlowType getType() { result = t } - - override TypedContent getHead() { none() } - - override AccessPath getTail() { none() } - - override AccessPathFrontNil getFront() { result = TFrontNil(t) } - - override AccessPathApproxNil getApprox() { result = TNil(t) } - - override int length() { result = 0 } - - override string toString() { result = concat(": " + ppReprType(t)) } -} - -private class AccessPathCons extends AccessPath, TAccessPathCons { - private TypedContent head; - private AccessPath tail; - - AccessPathCons() { this = TAccessPathCons(head, tail) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { result = tail } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - pragma[assume_small_delta] - override AccessPathApproxCons getApprox() { - result = TConsNil(head, tail.(AccessPathNil).getType()) - or - result = TConsCons(head, tail.getHead(), this.length()) - or - result = TCons1(head, this.length()) - } - - pragma[assume_small_delta] - override int length() { result = 1 + tail.length() } - - private string toStringImpl(boolean needsSuffix) { - exists(DataFlowType t | - tail = TAccessPathNil(t) and - needsSuffix = false and - result = head.toString() + "]" + concat(" : " + ppReprType(t)) - ) - or - result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) - or - exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | - result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true - or - result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false - ) - or - exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | - result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true - or - result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false - ) - } - - override string toString() { - result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" - or - result = "[" + this.toStringImpl(false) - } -} - -private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { - private TypedContent head1; - private TypedContent head2; - private int len; - - AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } - - override TypedContent getHead() { result = head1 } - - override AccessPath getTail() { - Stage5::consCand(head1, result.getApprox(), _) and - result.getHead() = head2 and - result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head1) } - - override AccessPathApproxCons getApprox() { - result = TConsCons(head1, head2, len) or - result = TCons1(head1, len) - } - - override int length() { result = len } - - override string toString() { - if len = 2 - then result = "[" + head1.toString() + ", " + head2.toString() + "]" - else - result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" - } -} - -private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { - private TypedContent head; - private int len; - - AccessPathCons1() { this = TAccessPathCons1(head, len) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { - Stage5::consCand(head, result.getApprox(), _) and result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - - override int length() { result = len } - - override string toString() { - if len = 1 - then result = "[" + head.toString() + "]" - else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" - } -} - -abstract private class PathNodeImpl extends TPathNode { - /** Gets the `FlowState` of this node. */ - abstract FlowState getState(); - - /** Gets the associated configuration. */ - abstract Configuration getConfiguration(); - - /** Holds if this node is a source. */ - abstract predicate isSource(); - - abstract PathNodeImpl getASuccessorImpl(); - - private PathNodeImpl getASuccessorIfHidden() { - this.isHidden() and - result = this.getASuccessorImpl() - } - - pragma[nomagic] - private PathNodeImpl getANonHiddenSuccessor0() { - result = this.getASuccessorIfHidden*() and - not result.isHidden() - } - - final PathNodeImpl getANonHiddenSuccessor() { - result = this.getASuccessorImpl().getANonHiddenSuccessor0() and - not this.isHidden() - } - - abstract NodeEx getNodeEx(); - - predicate isHidden() { - not this.getConfiguration().includeHiddenNodes() and - ( - hiddenNode(this.getNodeEx().asNode()) and - not this.isSource() and - not this instanceof PathNodeSink - or - this.getNodeEx() instanceof TNodeImplicitRead - ) - } - - string getSourceGroup() { - this.isSource() and - this.getConfiguration().sourceGrouping(this.getNodeEx().asNode(), result) + not singleConfiguration() and + getConfig(state1).isAdditionalFlowStep(node1, node2) and + state2 = state1 } - predicate isFlowSource() { - this.isSource() and not exists(this.getSourceGroup()) - or - this instanceof PathNodeSourceGroup + predicate allowImplicitRead(Node node, ContentSet c) { + any(Configuration config).allowImplicitRead(node, c) } - predicate isFlowSink() { - this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or - this instanceof PathNodeSinkGroup - } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } - private string ppAp() { - this instanceof PathNodeSink and result = "" - or - exists(string s | s = this.(PathNodeMid).getAp().toString() | - if s = "" then result = "" else result = " " + s - ) - } + FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } - private string ppCtx() { - this instanceof PathNodeSink and result = "" - or - result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + predicate sourceGrouping(Node source, string sourceGroup) { + any(Configuration config).sourceGrouping(source, sourceGroup) } - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + predicate sinkGrouping(Node sink, string sinkGroup) { + any(Configuration config).sinkGrouping(sink, sinkGroup) } -} - -/** Holds if `n` can reach a sink. */ -private predicate directReach(PathNodeImpl n) { - n instanceof PathNodeSink or - n instanceof PathNodeSinkGroup or - directReach(n.getANonHiddenSuccessor()) -} - -/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ -private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } -/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { - n1.getANonHiddenSuccessor() = n2 and directReach(n2) + predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() } } -private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) +private import Impl as I +import I /** * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. */ -class PathNode instanceof PathNodeImpl { - PathNode() { reach(this) } - +class PathNode instanceof I::PathNode { /** Gets a textual representation of this element. */ final string toString() { result = super.toString() } @@ -3406,1548 +358,39 @@ class PathNode instanceof PathNodeImpl { } /** Gets the underlying `Node`. */ - final Node getNode() { super.getNodeEx().projectToNode() = result } + final Node getNode() { result = super.getNode() } /** Gets the `FlowState` of this node. */ - final FlowState getState() { result = super.getState() } + final FlowState getState() { result = getState(super.getState()) } /** Gets the associated configuration. */ - final Configuration getConfiguration() { result = super.getConfiguration() } + final Configuration getConfiguration() { result = getConfig(super.getState()) } /** Gets a successor of this node, if any. */ - final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } + final PathNode getASuccessor() { result = super.getASuccessor() } /** Holds if this node is a source. */ final predicate isSource() { super.isSource() } /** Holds if this node is a grouping of source nodes. */ - final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group, _) } + final predicate isSourceGroup(string group) { super.isSourceGroup(group) } /** Holds if this node is a grouping of sink nodes. */ - final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group, _) } + final predicate isSinkGroup(string group) { super.isSinkGroup(group) } } -/** - * Provides the query predicates needed to include a graph in a path-problem query. - */ -module PathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } - - /** Holds if `n` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode n, string key, string val) { - key = "semmle.label" and val = n.toString() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { - Subpaths::subpaths(arg, par, ret, out) - } -} - -/** - * An intermediate flow graph node. This is a triple consisting of a `Node`, - * a `CallContext`, and a `Configuration`. - */ -private class PathNodeMid extends PathNodeImpl, TPathNodeMid { - NodeEx node; - FlowState state; - CallContext cc; - SummaryCtx sc; - AccessPath ap; - Configuration config; - - PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - SummaryCtx getSummaryCtx() { result = sc } - - AccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - private PathNodeMid getSuccMid() { - pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx(), result.getAp()) and - result.getConfiguration() = unbindConf(this.getConfiguration()) - } - - override PathNodeImpl getASuccessorImpl() { - // an intermediate step to another intermediate node - result = this.getSuccMid() - or - // a final step to a sink - result = this.getSuccMid().projectToSink() - } - - override predicate isSource() { - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - } - - predicate isAtSink() { - sinkNode(node, state, config) and - ap instanceof AccessPathNil and - if hasSinkCallCtx(config) - then - // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` - // is exactly what we need to check. This also implies - // `sc instanceof SummaryCtxNone`. - // For `FeatureEqualSourceSinkCallContext` the initial call context was - // set to `CallContextSomeCall` and jumps are disallowed, so - // `cc instanceof CallContextNoCall` never holds. On the other hand, - // in this case there's never any need to enter a call except to identify - // a summary, so the condition in `pathIntoCallable` enforces this, which - // means that `sc instanceof SummaryCtxNone` holds if and only if we are - // in the call context of the source. - sc instanceof SummaryCtxNone or - cc instanceof CallContextNoCall - else any() - } - - PathNodeSink projectToSink() { - this.isAtSink() and - result.getNodeEx() = node and - result.getState() = state and - result.getConfiguration() = unbindConf(config) - } -} - -/** - * A flow graph node corresponding to a sink. This is disjoint from the - * intermediate nodes in order to uniquely correspond to a given sink by - * excluding the `CallContext`. - */ -private class PathNodeSink extends PathNodeImpl, TPathNodeSink { - NodeEx node; - FlowState state; - Configuration config; - - PathNodeSink() { this = TPathNodeSink(node, state, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result = TPathNodeSinkGroup(this.getSinkGroup(), config) - } - - override predicate isSource() { sourceNode(node, state, config) } - - string getSinkGroup() { config.sinkGrouping(node.asNode(), result) } -} - -private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { - string sourceGroup; - Configuration config; - - PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result.getSourceGroup() = sourceGroup and - result.getConfiguration() = config - } - - override predicate isSource() { none() } - - override string toString() { result = sourceGroup } - - override 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 - } -} - -private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { - string sinkGroup; - Configuration config; - - PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { none() } - - override predicate isSource() { none() } - - override string toString() { result = sinkGroup } - - override 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 - } -} - -private predicate pathNode( - PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, - Configuration conf, LocalCallContext localCC -) { - midnode = mid.getNodeEx() and - state = mid.getState() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - midnode.getEnclosingCallable()) and - ap = mid.getAp() -} - -/** - * Holds if data may flow from `mid` to `node`. The last step in or out of - * a callable is recorded by `cc`. - */ -pragma[assume_small_delta] -pragma[nomagic] -private predicate pathStep( - PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap -) { - exists(NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC | - pathNode(mid, midnode, state0, cc, sc, ap, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, true, _, conf, localCC) - ) - or - exists( - AccessPath ap0, NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC - | - pathNode(mid, midnode, state0, cc, sc, ap0, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), conf, - localCC) and - ap0 instanceof AccessPathNil - ) - or - jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - ap = mid.getAp() - or - additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, mid.getConfiguration()) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - pathIntoCallable(mid, node, state, _, cc, sc, _, _) and ap = mid.getAp() - or - pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone - or - pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() -} - -pragma[nomagic] -private predicate pathReadStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - tc = ap0.getHead() and - Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -pragma[nomagic] -private predicate pathStoreStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -private predicate pathOutOfCallable0( - PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa, - Configuration config -) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - apa = mid.getAp().getApprox() and - config = mid.getConfiguration() -} - -pragma[nomagic] -private predicate pathOutOfCallable1( - PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPathApprox apa, Configuration config -) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - pathOutOfCallable0(mid, pos, state, innercc, apa, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() +private predicate hasFlow(Node source, Node sink, Configuration config) { + exists(PathNode source0, PathNode sink0 | + hasFlowPath(source0, sink0, config) and + source0.getNode() = source and + sink0.getNode() = sink ) } -pragma[noinline] -private NodeEx getAnOutNodeFlow( - ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config -) { - result.asNode() = kind.getAnOutNode(call) and - Stage5::revFlow(result, _, apa, config) +private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) { + hasFlowPath(source, sink) and source.getConfiguration() = config } -/** - * Holds if data may flow from `mid` to `out`. The last step of this path - * is a return from a callable and is recorded by `cc`, if needed. - */ -pragma[noinline] -private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { - exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | - pathOutOfCallable1(mid, call, kind, state, cc, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} +private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) } -/** - * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. - */ -pragma[noinline] -private predicate pathIntoArg( - PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(ArgNodeEx arg, ArgumentPosition apos | - pathNode(mid, arg, state, cc, _, ap, config, _) and - arg.asNode().(ArgNode).argumentOf(call, apos) and - apa = ap.getApprox() and - parameterMatch(ppos, apos) - ) -} - -pragma[nomagic] -private predicate parameterCand( - DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::revFlow(p, _, apa, config) and - p.isParameterOf(callable, pos) - ) -} - -pragma[nomagic] -private predicate pathIntoCallable0( - PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, AccessPath ap, Configuration config -) { - exists(AccessPathApprox apa | - pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - callable = resolveCall(call, outercc) and - parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa), - pragma[only_bind_into](config)) - ) -} - -/** - * Holds if data may flow from `mid` to `p` through `call`. The contexts - * before and after entering the callable are `outercc` and `innercc`, - * respectively. - */ -pragma[nomagic] -private predicate pathIntoCallable( - PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, - SummaryCtx sc, DataFlowCall call, Configuration config -) { - exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | - pathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - ( - sc = TSummaryCtxSome(p, state, ap) - or - not exists(TSummaryCtxSome(p, state, ap)) and - sc = TSummaryCtxNone() and - // When the call contexts of source and sink needs to match then there's - // never any reason to enter a callable except to find a summary. See also - // the comment in `PathNodeMid::isAtSink`. - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) -} - -/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ -pragma[nomagic] -private predicate paramFlowsThrough( - ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, - AccessPathApprox apa, Configuration config -) { - exists(RetNodeEx ret | - pathNode(_, ret, state, cc, sc, ap, config, _) and - kind = ret.getKind() and - apa = ap.getApprox() and - parameterFlowThroughAllowed(sc.getParamNode(), kind) - ) -} - -pragma[nomagic] -private predicate pathThroughCallable0( - DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(CallContext innercc, SummaryCtx sc | - pathIntoCallable(mid, _, _, cc, innercc, sc, call, config) and - paramFlowsThrough(kind, state, innercc, sc, ap, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` through a callable to the node `out`. - * The context `cc` is restored to its value prior to entering the callable. - */ -pragma[noinline] -private predicate pathThroughCallable( - PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap -) { - exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config | - pathThroughCallable0(call, mid, kind, state, cc, ap, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -private module Subpaths { - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths01( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - exists(Configuration config | - pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and - pathIntoCallable(arg, par, _, _, innercc, sc, _, config) and - paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, - pragma[only_bind_into](apout), _, unbindConf(config)) and - not arg.isHidden() - ) - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `sout`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths02( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and - out.asNode() = kind.getAnOutNode(_) - } - - pragma[nomagic] - private Configuration getPathNodeConf(PathNodeImpl n) { result = n.getConfiguration() } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple. - */ - pragma[nomagic] - private predicate subpaths03( - PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, AccessPath apout - ) { - exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | - subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and - pathNode(ret, retnode, sout, innercc, sc, apout, unbindConf(getPathNodeConf(arg)), _) and - kind = retnode.getKind() - ) - } - - private PathNodeImpl localStepToHidden(PathNodeImpl n) { - n.getASuccessorImpl() = result and - result.isHidden() and - exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | - localFlowBigStep(n1, _, n2, _, _, _, _, _) or - store(n1, _, n2, _, _) or - readSet(n1, _, n2, _) - ) - } - - pragma[nomagic] - private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { - succ = pred.getANonHiddenSuccessor() and - succNode = succ.getNodeEx() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { - exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | - pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and - subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and - hasSuccessor(pragma[only_bind_into](arg), par, p) and - not ret.isHidden() and - pathNode(out0, o, sout, _, _, apout, _, _) - | - out = out0 or out = out0.projectToSink() - ) - } - - /** - * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. - */ - predicate retReach(PathNodeImpl n) { - exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) - or - exists(PathNodeImpl mid | - retReach(mid) and - n.getANonHiddenSuccessor() = mid and - not subpaths(_, mid, _, _) - ) - } -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -private predicate hasFlowPath( - PathNodeImpl flowsource, PathNodeImpl flowsink, Configuration configuration -) { - flowsource.isFlowSource() and - flowsource.getConfiguration() = configuration and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.isFlowSink() -} - -private predicate flowsTo( - PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink, - Configuration configuration -) { - flowsource.isSource() and - flowsource.getConfiguration() = configuration and - flowsource.getNodeEx().asNode() = source and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.getNodeEx().asNode() = sink -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -predicate flowsTo(Node source, Node sink, Configuration configuration) { - flowsTo(_, _, source, sink, configuration) -} - -private predicate finalStats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples -) { - fwd = true and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and - tuples = count(PathNodeImpl pn) - or - fwd = false and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and - tuples = count(PathNode pn) -} - -/** - * INTERNAL: Only for debugging. - * - * Calculates per-stage metrics for data flow. - */ -predicate stageStats( - int n, string stage, int nodes, int fields, int conscand, int states, int tuples, - Configuration config -) { - stage = "1 Fwd" and - n = 10 and - Stage1::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "1 Rev" and - n = 15 and - Stage1::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Fwd" and - n = 20 and - Stage2::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Rev" and - n = 25 and - Stage2::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Fwd" and - n = 30 and - Stage3::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Rev" and - n = 35 and - Stage3::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Fwd" and - n = 40 and - Stage4::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Rev" and - n = 45 and - Stage4::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Fwd" and - n = 50 and - Stage5::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Rev" and - n = 55 and - Stage5::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) - or - stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) -} - -private module FlowExploration { - private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - jumpStep(node1, node2, config) - or - additionalJumpStep(node1, node2, config) - or - additionalJumpStateStep(node1, _, node2, _, config) - or - // flow into callable - viableParamArgEx(_, node2, node1) - or - // flow out of a callable - viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) - | - c1 = node1.getEnclosingCallable() and - c2 = node2.getEnclosingCallable() and - c1 != c2 - ) - } - - private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSource(n) or config.isSource(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSrc(mid, config) and callableStep(mid, c, config) - ) - } - - private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSink(n) or config.isSink(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSink(mid, config) and callableStep(c, mid, config) - ) - } - - private newtype TCallableExt = - TCallable(DataFlowCallable c, Configuration config) { - interestingCallableSrc(c, config) or - interestingCallableSink(c, config) - } or - TCallableSrc() or - TCallableSink() - - private predicate callableExtSrc(TCallableSrc src) { any() } - - private predicate callableExtSink(TCallableSink sink) { any() } - - private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { - exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | - callableStep(c1, c2, config) and - ce1 = TCallable(c1, pragma[only_bind_into](config)) and - ce2 = TCallable(c2, pragma[only_bind_into](config)) - ) - or - exists(Node n, Configuration config | - ce1 = TCallableSrc() and - (config.isSource(n) or config.isSource(n, _)) and - ce2 = TCallable(getNodeEnclosingCallable(n), config) - ) - or - exists(Node n, Configuration config | - ce2 = TCallableSink() and - (config.isSink(n) or config.isSink(n, _)) and - ce1 = TCallable(getNodeEnclosingCallable(n), config) - ) - } - - private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { - callableExtStepFwd(ce2, ce1) - } - - private int distSrcExt(TCallableExt c) = - shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) - - private int distSinkExt(TCallableExt c) = - shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) - - private int distSrc(DataFlowCallable c, Configuration config) { - result = distSrcExt(TCallable(c, config)) - 1 - } - - private int distSink(DataFlowCallable c, Configuration config) { - result = distSinkExt(TCallable(c, config)) - 1 - } - - private newtype TPartialAccessPath = - TPartialNil(DataFlowType t) or - TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first - * element of the list and its length are tracked. If data flows from a source to - * a given node with a given `AccessPath`, this indicates the sequence of - * dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ - private class PartialAccessPath extends TPartialAccessPath { - abstract string toString(); - - TypedContent getHead() { this = TPartialCons(result, _) } - - int len() { - this = TPartialNil(_) and result = 0 - or - this = TPartialCons(_, result) - } - - DataFlowType getType() { - this = TPartialNil(result) - or - exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) - } - } - - private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { - override string toString() { - exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) - } - } - - private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { - override string toString() { - exists(TypedContent tc, int len | this = TPartialCons(tc, len) | - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private newtype TRevPartialAccessPath = - TRevPartialNil() or - TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `Content`s, but only the first - * element of the list and its length are tracked. - */ - private class RevPartialAccessPath extends TRevPartialAccessPath { - abstract string toString(); - - Content getHead() { this = TRevPartialCons(result, _) } - - int len() { - this = TRevPartialNil() and result = 0 - or - this = TRevPartialCons(_, result) - } - } - - private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { - override string toString() { result = "" } - } - - private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { - override string toString() { - exists(Content c, int len | this = TRevPartialCons(c, len) | - if len = 1 - then result = "[" + c.toString() + "]" - else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private predicate relevantState(FlowState state) { - sourceNode(_, state, _) or - sinkNode(_, state, _) or - additionalLocalStateStep(_, state, _, _, _) or - additionalLocalStateStep(_, _, _, state, _) or - additionalJumpStateStep(_, state, _, _, _) or - additionalJumpStateStep(_, _, _, state, _) - } - - private newtype TSummaryCtx1 = - TSummaryCtx1None() or - TSummaryCtx1Param(ParamNodeEx p) - - private newtype TSummaryCtx2 = - TSummaryCtx2None() or - TSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TSummaryCtx3 = - TSummaryCtx3None() or - TSummaryCtx3Some(PartialAccessPath ap) - - private newtype TRevSummaryCtx1 = - TRevSummaryCtx1None() or - TRevSummaryCtx1Some(ReturnPosition pos) - - private newtype TRevSummaryCtx2 = - TRevSummaryCtx2None() or - TRevSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TRevSummaryCtx3 = - TRevSummaryCtx3None() or - TRevSummaryCtx3Some(RevPartialAccessPath ap) - - private newtype TPartialPathNode = - TPartialPathNodeFwd( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = TPartialNil(node.getDataFlowType()) and - exists(config.explorationLimit()) - or - partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap, config) and - distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() - } or - TPartialPathNodeRev( - NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, - RevPartialAccessPath ap, Configuration config - ) { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() and - exists(config.explorationLimit()) - or - revPartialPathStep(_, node, state, sc1, sc2, sc3, ap, config) and - not clearsContentEx(node, ap.getHead()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead()) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() - } - - pragma[nomagic] - private predicate partialPathNodeMk0( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap, config) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - not clearsContentEx(node, ap.getHead().getContent()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead().getContent()) - ) and - if node.asNode() instanceof CastingNode - then compatibleTypes(node.getDataFlowType(), ap.getType()) - else any() - } - - /** - * A `Node` augmented with a call context, an access path, and a configuration. - */ - class PartialPathNode extends TPartialPathNode { - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() - } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - /** Gets the underlying `Node`. */ - final Node getNode() { this.getNodeEx().projectToNode() = result } - - FlowState getState() { none() } - - private NodeEx getNodeEx() { - result = this.(PartialPathNodeFwd).getNodeEx() or - result = this.(PartialPathNodeRev).getNodeEx() - } - - /** Gets the associated configuration. */ - Configuration getConfiguration() { none() } - - /** Gets a successor of this node, if any. */ - PartialPathNode getASuccessor() { none() } - - /** - * Gets the approximate distance to the nearest source measured in number - * of interprocedural steps. - */ - int getSourceDistance() { - result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - /** - * Gets the approximate distance to the nearest sink measured in number - * of interprocedural steps. - */ - int getSinkDistance() { - result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - private string ppAp() { - exists(string s | - s = this.(PartialPathNodeFwd).getAp().toString() or - s = this.(PartialPathNodeRev).getAp().toString() - | - if s = "" then result = "" else result = " " + s - ) - } - - private string ppCtx() { - result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" - } - - /** Holds if this is a source in a forward-flow path. */ - predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } - - /** Holds if this is a sink in a reverse-flow path. */ - predicate isRevSink() { this.(PartialPathNodeRev).isSink() } - } - - /** - * Provides the query predicates needed to include a graph in a path-problem query. - */ - module PartialPathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } - } - - private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { - NodeEx node; - FlowState state; - CallContext cc; - TSummaryCtx1 sc1; - TSummaryCtx2 sc2; - TSummaryCtx3 sc3; - PartialAccessPath ap; - Configuration config; - - PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - TSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TSummaryCtx3 getSummaryCtx3() { result = sc3 } - - PartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeFwd getASuccessor() { - partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp(), - result.getConfiguration()) - } - - predicate isSource() { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap instanceof TPartialNil - } - } - - private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { - NodeEx node; - FlowState state; - TRevSummaryCtx1 sc1; - TRevSummaryCtx2 sc2; - TRevSummaryCtx3 sc3; - RevPartialAccessPath ap; - Configuration config; - - PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } - - RevPartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeRev getASuccessor() { - revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), - this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp(), this.getConfiguration()) - } - - predicate isSink() { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() - } - } - - private predicate partialPathStep( - PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, - TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and - ( - localFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - ) - or - jumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - partialPathStoreStep(mid, _, _, node, ap) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(PartialAccessPath ap0, TypedContent tc | - partialPathReadStep(mid, ap0, tc, node, cc, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsFwd(ap, tc, ap0, config) - ) - or - partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap, config) - or - partialPathOutOfCallable(mid, node, state, cc, ap, config) and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() - or - partialPathThroughCallable(mid, node, state, cc, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - bindingset[result, i] - private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } - - pragma[inline] - private predicate partialPathStoreStep( - PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, - PartialAccessPath ap2 - ) { - exists(NodeEx midNode, DataFlowType contentType | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - store(midNode, tc, node, contentType, mid.getConfiguration()) and - ap2.getHead() = tc and - ap2.len() = unbindInt(ap1.len() + 1) and - compatibleTypes(ap1.getType(), contentType) - ) - } - - pragma[nomagic] - private predicate apConsFwd( - PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeFwd mid | - partialPathStoreStep(mid, ap1, tc, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate partialPathReadStep( - PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc, - Configuration config - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and - ap.getHead() = tc and - pragma[only_bind_into](config) = mid.getConfiguration() and - cc = mid.getCallContext() - ) - } - - private predicate partialPathOutOfCallable0( - PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, - PartialAccessPath ap, Configuration config - ) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - ap = mid.getAp() and - config = mid.getConfiguration() - } - - pragma[nomagic] - private predicate partialPathOutOfCallable1( - PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - partialPathOutOfCallable0(mid, pos, state, innercc, ap, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) - } - - private predicate partialPathOutOfCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(ReturnKindExt kind, DataFlowCall call | - partialPathOutOfCallable1(mid, call, kind, state, cc, ap, config) - | - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[noinline] - private predicate partialPathIntoArg( - PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ArgNode arg, ArgumentPosition apos | - arg = mid.getNodeEx().asNode() and - state = mid.getState() and - cc = mid.getCallContext() and - arg.argumentOf(call, apos) and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate partialPathIntoCallable0( - PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - partialPathIntoArg(mid, pos, state, outercc, call, ap, config) and - callable = resolveCall(call, outercc) - } - - private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, - CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ParameterPosition pos, DataFlowCallable callable | - partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - sc1 = TSummaryCtx1Param(p) and - sc2 = TSummaryCtx2Some(state) and - sc3 = TSummaryCtx3Some(ap) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) - } - - pragma[nomagic] - private predicate paramFlowsThroughInPartialPath( - ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeFwd mid, RetNodeEx ret | - mid.getNodeEx() = ret and - kind = ret.getKind() and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() and - ap = mid.getAp() - ) - } - - pragma[noinline] - private predicate partialPathThroughCallable0( - DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | - partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _, config) and - paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap, config) - ) - } - - private predicate partialPathThroughCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ReturnKindExt kind | - partialPathThroughCallable0(call, mid, kind, state, cc, ap, config) and - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[nomagic] - private predicate revPartialPathStep( - PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, - TRevSummaryCtx3 sc3, RevPartialAccessPath ap, Configuration config - ) { - localFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - jumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - revPartialPathReadStep(mid, _, _, node, ap) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(RevPartialAccessPath ap0, Content c | - revPartialPathStoreStep(mid, ap0, c, node, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsRev(ap, c, ap0, config) - ) - or - exists(ParamNodeEx p | - mid.getNodeEx() = p and - viableParamArgEx(_, p, node) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - or - exists(ReturnPosition pos | - revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap, config) and - pos = getReturnPosition(node.asNode()) - ) - or - revPartialPathThroughCallable(mid, node, state, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - pragma[inline] - private predicate revPartialPathReadStep( - PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, - RevPartialAccessPath ap2 - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - read(node, c, midNode, mid.getConfiguration()) and - ap2.getHead() = c and - ap2.len() = unbindInt(ap1.len() + 1) - ) - } - - pragma[nomagic] - private predicate apConsRev( - RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeRev mid | - revPartialPathReadStep(mid, ap1, c, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathStoreStep( - PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config - ) { - exists(NodeEx midNode, TypedContent tc | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - store(node, tc, midNode, _, config) and - ap.getHead() = c and - config = mid.getConfiguration() and - tc.getContent() = c - ) - } - - pragma[nomagic] - private predicate revPartialPathIntoReturn( - PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, - TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap, - Configuration config - ) { - exists(NodeEx out | - mid.getNodeEx() = out and - mid.getState() = state and - viableReturnPosOutEx(call, pos, out) and - sc1 = TRevSummaryCtx1Some(pos) and - sc2 = TRevSummaryCtx2Some(state) and - sc3 = TRevSummaryCtx3Some(ap) and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathFlowsThrough( - ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, - TRevSummaryCtx3Some sc3, RevPartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | - mid.getNodeEx() = p and - mid.getState() = state and - p.getPosition() = ppos and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable0( - DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, - RevPartialAccessPath ap, Configuration config - ) { - exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | - revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _, config) and - revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap, config) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ArgumentPosition pos | - revPartialPathThroughCallable0(call, mid, pos, state, ap, config) and - node.asNode().(ArgNode).argumentOf(call, pos) - ) - } -} - -import FlowExploration - -private predicate partialFlow( - PartialPathNode source, PartialPathNode node, Configuration configuration -) { - source.getConfiguration() = configuration and - source.isFwdSource() and - node = source.getASuccessor+() -} - -private predicate revPartialFlow( - PartialPathNode node, PartialPathNode sink, Configuration configuration -) { - sink.getConfiguration() = configuration and - sink.isRevSink() and - node.getASuccessor+() = sink -} +predicate flowsTo = hasFlow/3; diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll index 1b969756b09..e6bdc74cceb 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll @@ -1,16 +1,16 @@ /** - * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from - * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed - * through the `Configuration` class. This file exists in several identical - * copies, allowing queries to use multiple `Configuration` classes that depend - * on each other without introducing mutual recursion among those configurations. + * DEPRECATED: Use `Make` and `MakeWithState` instead. + * + * Provides a `Configuration` class backwards-compatible interface to the data + * flow library. */ private import DataFlowImplCommon private import DataFlowImplSpecific::Private import DataFlowImplSpecific::Public +private import DataFlowImpl import DataFlowImplCommonPublic +import FlowStateString /** * A configuration of interprocedural data flow analysis. This defines @@ -144,6 +144,10 @@ abstract class Configuration extends string { * - `FeatureEqualSourceSinkCallContext`: * Implies both of the above and additionally ensures that the entire flow * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. */ FlowFeature getAFeature() { none() } @@ -156,7 +160,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from `source` to `sink` for this configuration. */ - predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } + predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) } /** * Holds if data may flow from `source` to `sink` for this configuration. @@ -169,9 +173,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from some source to `sink` for this configuration. */ - predicate hasFlowTo(Node sink) { - sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode() - } + predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) } /** * Holds if data may flow from some source to `sink` for this configuration. @@ -179,10 +181,12 @@ abstract class Configuration extends string { predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } /** + * DEPRECATED: Use `FlowExploration` instead. + * * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` * measured in approximate number of interprocedural steps. */ - int explorationLimit() { none() } + deprecated int explorationLimit() { none() } /** * Holds if hidden nodes should be included in the data flow graph. @@ -191,49 +195,6 @@ abstract class Configuration extends string { * is not visualized (for example in a `path-problem` query). */ predicate includeHiddenNodes() { none() } - - /** - * Holds if there is a partial data flow path from `source` to `node`. The - * approximate distance between `node` and the closest source is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards sink definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sources is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - */ - final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { - partialFlow(source, node, this) and - dist = node.getSourceDistance() - } - - /** - * Holds if there is a partial data flow path from `node` to `sink`. The - * approximate distance between `node` and the closest sink is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards source definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sinks is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - * - * Note that reverse flow has slightly lower precision than the corresponding - * forward flow, as reverse flow disregards type pruning among other features. - */ - final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { - revPartialFlow(node, sink, this) and - dist = node.getSinkDistance() - } } /** @@ -263,90 +224,6 @@ abstract private class ConfigurationRecursionPrevention extends Configuration { } } -private newtype TNodeEx = - TNodeNormal(Node n) or - TNodeImplicitRead(Node n, boolean hasRead) { - any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true] - } - -private class NodeEx extends TNodeEx { - string toString() { - result = this.asNode().toString() - or - exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") - } - - Node asNode() { this = TNodeNormal(result) } - - predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } - - Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } - - pragma[nomagic] - private DataFlowCallable getEnclosingCallable0() { - nodeEnclosingCallable(this.projectToNode(), result) - } - - pragma[inline] - DataFlowCallable getEnclosingCallable() { - pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) - } - - pragma[nomagic] - private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } - - pragma[inline] - DataFlowType getDataFlowType() { - pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) - } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -private class ArgNodeEx extends NodeEx { - ArgNodeEx() { this.asNode() instanceof ArgNode } -} - -private class ParamNodeEx extends NodeEx { - ParamNodeEx() { this.asNode() instanceof ParamNode } - - predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { - this.asNode().(ParamNode).isParameterOf(c, pos) - } - - ParameterPosition getPosition() { this.isParameterOf(_, result) } -} - -private class RetNodeEx extends NodeEx { - RetNodeEx() { this.asNode() instanceof ReturnNodeExt } - - ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } - - ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } -} - -private predicate inBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierIn(n) - | - config.isSource(n) or config.isSource(n, _) - ) -} - -private predicate outBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierOut(n) - | - config.isSink(n) or config.isSink(n, _) - ) -} - /** A bridge class to access the deprecated `isBarrierGuard`. */ private class BarrierGuardGuardedNodeBridge extends Unit { abstract predicate guardedNode(Node n, Configuration config); @@ -370,3019 +247,94 @@ private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { } } -pragma[nomagic] -private predicate fullBarrier(NodeEx node, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n) +private FlowState relevantState(Configuration config) { + config.isSource(_, result) or + config.isSink(_, result) or + config.isBarrier(_, result) or + config.isAdditionalFlowStep(_, result, _, _) or + config.isAdditionalFlowStep(_, _, _, result) +} + +private newtype TConfigState = + TMkConfigState(Configuration config, FlowState state) { + state = relevantState(config) or state instanceof FlowStateEmpty + } + +private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) } + +private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) } + +private predicate singleConfiguration() { 1 = strictcount(Configuration c) } + +private module Config implements FullStateConfigSig { + class FlowState = TConfigState; + + predicate isSource(Node source, FlowState state) { + getConfig(state).isSource(source, getState(state)) or - config.isBarrierIn(n) and - not config.isSource(n) and - not config.isSource(n, _) + getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty + } + + predicate isSink(Node sink, FlowState state) { + getConfig(state).isSink(sink, getState(state)) or - config.isBarrierOut(n) and - not config.isSink(n) and - not config.isSink(n, _) + getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty + } + + predicate isBarrier(Node node) { none() } + + predicate isBarrier(Node node, FlowState state) { + getConfig(state).isBarrier(node, getState(state)) or + getConfig(state).isBarrier(node) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state)) + } + + predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) } + + predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) } + + predicate isAdditionalFlowStep(Node node1, Node node2) { + singleConfiguration() and + any(Configuration config).isAdditionalFlowStep(node1, node2) + } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and + getConfig(state2) = getConfig(state1) or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config) - ) -} - -pragma[nomagic] -private predicate stateBarrier(NodeEx node, FlowState state, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n, state) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config) - ) -} - -pragma[nomagic] -private predicate sourceNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSource(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSource(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -pragma[nomagic] -private predicate sinkNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSink(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSink(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -/** Provides the relevant barriers for a step from `node1` to `node2`. */ -pragma[inline] -private predicate stepFilter(NodeEx node1, NodeEx node2, Configuration config) { - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) -} - -/** - * Holds if data can flow in one local step from `node1` to `node2`. - */ -private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.asNode() = n and - node2.isImplicitReadNode(n, false) and - not fullBarrier(node1, config) - ) -} - -/** - * Holds if the additional step from `node1` to `node2` does not jump between callables. - */ -private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.isImplicitReadNode(n, true) and - node2.asNode() = n and - not fullBarrier(node2, config) - ) -} - -private predicate additionalLocalStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) - ) -} - -/** - * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. - */ -private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if the additional step from `node1` to `node2` jumps between callables. - */ -private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate additionalJumpStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -pragma[nomagic] -private predicate readSet(NodeEx node1, ContentSet c, NodeEx node2, Configuration config) { - readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and - stepFilter(node1, node2, config) - or - exists(Node n | - node2.isImplicitReadNode(n, true) and - node1.isImplicitReadNode(n, _) and - config.allowImplicitRead(n, c) - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(ContentSet cs | - readSet(node1, cs, node2, config) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate clearsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - clearsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate expectsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - expectsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -pragma[nomagic] -private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } - -pragma[nomagic] -private predicate hasReadStep(Content c, Configuration config) { read(_, c, _, config) } - -pragma[nomagic] -private predicate store( - NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config -) { - store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), - contentType) and - hasReadStep(tc.getContent(), config) and - stepFilter(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { - viableReturnPosOut(call, pos, out.asNode()) -} - -pragma[nomagic] -private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { - viableParamArg(call, p.asNode(), arg.asNode()) -} - -/** - * Holds if field flow should be used for the given configuration. - */ -private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } - -private predicate hasSourceCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSourceCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate hasSinkCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSinkCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if flow from `p` to a return node of kind `kind` is allowed. - * - * We don't expect a parameter to return stored in itself, unless - * explicitly allowed - */ -bindingset[p, kind] -private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { - exists(ParameterPosition pos | p.isParameterOf(_, pos) | - not kind.(ParamUpdateReturnKind).getPosition() = pos - or - allowParameterReturnInSelfCached(p.asNode()) - ) -} - -private module Stage1 implements StageSig { - class Ap extends int { - // workaround for bad functionality-induced joins (happens when using `Unit`) - pragma[nomagic] - Ap() { this in [0 .. 1] and this < 1 } - } - - private class Cc = boolean; - - /* Begin: Stage 1 logic. */ - /** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `cc` records whether the node is reached through an - * argument in a call. - */ - private predicate fwdFlow(NodeEx node, Cc cc, Configuration config) { - sourceNode(node, _, config) and - if hasSourceCallCtx(config) then cc = true else cc = false - or - exists(NodeEx mid | fwdFlow(mid, cc, config) | - localFlowStep(mid, node, config) or - additionalLocalFlowStep(mid, node, config) or - additionalLocalStateStep(mid, _, node, _, config) - ) - or - exists(NodeEx mid | fwdFlow(mid, _, config) and cc = false | - jumpStep(mid, node, config) or - additionalJumpStep(mid, node, config) or - additionalJumpStateStep(mid, _, node, _, config) - ) - or - // store - exists(NodeEx mid | - useFieldFlow(config) and - fwdFlow(mid, cc, config) and - store(mid, _, node, _, config) - ) - or - // read - exists(ContentSet c | - fwdFlowReadSet(c, node, cc, config) and - fwdFlowConsCandSet(c, _, config) - ) - or - // flow into a callable - fwdFlowIn(_, _, _, node, config) and - cc = true - or - // flow out of a callable - fwdFlowOut(_, node, false, config) and - cc = false - or - // flow through a callable - exists(DataFlowCall call | - fwdFlowOutFromArg(call, node, config) and - fwdFlowIsEntered(call, cc, config) - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowIn( - DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p, Configuration config - ) { - // call context cannot help reduce virtual dispatch - fwdFlow(arg, cc, config) and - viableParamArgEx(call, p, arg) and - not fullBarrier(p, config) and - ( - cc = false - or - cc = true and - not reducedViableImplInCallContext(call, _, _) - ) - or - // call context may help reduce virtual dispatch - exists(DataFlowCallable target | - fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target, config) and - target = viableImplInSomeFwdFlowCallContextExt(call, config) and - cc = true - ) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - fwdFlowIn(call, _, cc, _, config) - } - - pragma[nomagic] - private predicate fwdFlowInReducedViableImplInSomeCallContext( - DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target, Configuration config - ) { - fwdFlow(arg, true, config) and - viableParamArgEx(call, p, arg) and - reducedViableImplInCallContext(call, _, _) and - target = p.getEnclosingCallable() and - not fullBarrier(p, config) - } - - /** - * Gets a viable dispatch target of `call` in the context `ctx`. This is - * restricted to those `call`s for which a context might make a difference, - * and to `ctx`s that are reachable in `fwdFlow`. - */ - pragma[nomagic] - private DataFlowCallable viableImplInSomeFwdFlowCallContextExt( - DataFlowCall call, Configuration config - ) { - exists(DataFlowCall ctx | - fwdFlowIsEntered(ctx, _, config) and - result = viableImplInCallContextExt(call, ctx) - ) - } - - private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) } - - pragma[nomagic] - private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc, Configuration config) { - exists(NodeEx mid | - fwdFlow(mid, cc, config) and - readSet(mid, c, node, config) - ) - } - - /** - * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - fwdFlow(mid, _, config) and - store(mid, tc, node, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `cs` may be interpreted in a read as the target of some store - * into `c`, in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCandSet(ContentSet cs, Content c, Configuration config) { - fwdFlowConsCand(c, config) and - c = cs.getAReadContent() - } - - pragma[nomagic] - private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { - exists(RetNodeEx ret | - fwdFlow(ret, cc, config) and - ret.getReturnPosition() = pos - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) { - exists(ReturnPosition pos | - fwdFlowReturnPosition(pos, cc, config) and - viableReturnPosOutEx(call, pos, out) and - not fullBarrier(out, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) { - fwdFlowOut(call, out, true, config) - } - - private predicate stateStepFwd(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1 | - additionalLocalStateStep(node1, state1, _, state2, config) or - additionalJumpStateStep(node1, state1, _, state2, config) - | - fwdFlow(node1, config) - ) - } - - private predicate fwdFlowState(FlowState state, Configuration config) { - sourceNode(_, state, config) - or - exists(FlowState state0 | - fwdFlowState(state0, config) and - stateStepFwd(state0, state, config) - ) - } - - /** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink. - */ - pragma[nomagic] - private predicate revFlow(NodeEx node, boolean toReturn, Configuration config) { - revFlow0(node, toReturn, config) and - fwdFlow(node, config) - } - - pragma[nomagic] - private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) { - exists(FlowState state | - fwdFlow(node, pragma[only_bind_into](config)) and - sinkNode(node, state, config) and - fwdFlowState(state, pragma[only_bind_into](config)) and - if hasSinkCallCtx(config) then toReturn = true else toReturn = false - ) - or - exists(NodeEx mid | revFlow(mid, toReturn, config) | - localFlowStep(node, mid, config) or - additionalLocalFlowStep(node, mid, config) or - additionalLocalStateStep(node, _, mid, _, config) - ) - or - exists(NodeEx mid | revFlow(mid, _, config) and toReturn = false | - jumpStep(node, mid, config) or - additionalJumpStep(node, mid, config) or - additionalJumpStateStep(node, _, mid, _, config) - ) - or - // store - exists(Content c | - revFlowStore(c, node, toReturn, config) and - revFlowConsCand(c, config) - ) - or - // read - exists(NodeEx mid, ContentSet c | - readSet(node, c, mid, config) and - fwdFlowConsCandSet(c, _, pragma[only_bind_into](config)) and - revFlow(mid, toReturn, pragma[only_bind_into](config)) - ) - or - // flow into a callable - revFlowIn(_, node, false, config) and - toReturn = false - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(pos, config) and - node.(RetNodeEx).getReturnPosition() = pos and - toReturn = true - ) - or - // flow through a callable - exists(DataFlowCall call | - revFlowInToReturn(call, node, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - /** - * Holds if `c` is the target of a read in the flow covered by `revFlow`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, ContentSet cs | - fwdFlow(node, pragma[only_bind_into](config)) and - readSet(node, cs, mid, config) and - fwdFlowConsCandSet(cs, c, pragma[only_bind_into](config)) and - revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) { - exists(NodeEx mid, TypedContent tc | - revFlow(mid, toReturn, pragma[only_bind_into](config)) and - fwdFlowConsCand(c, pragma[only_bind_into](config)) and - store(node, tc, mid, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `c` is the target of both a read and a store in the flow covered - * by `revFlow`. - */ - pragma[nomagic] - additional predicate revFlowIsReadAndStored(Content c, Configuration conf) { - revFlowConsCand(c, conf) and - revFlowStore(c, _, _, conf) - } - - pragma[nomagic] - additional predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config - ) { - fwdFlowReturnPosition(pos, _, config) and - viableReturnPosOutEx(call, pos, out) - } - - pragma[nomagic] - private predicate revFlowOut(ReturnPosition pos, Configuration config) { - exists(NodeEx out | - revFlow(out, _, config) and - viableReturnPosOutNodeCandFwd1(_, pos, out, config) - ) - } - - pragma[nomagic] - additional predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config - ) { - fwdFlowIn(call, arg, _, p, config) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate revFlowIn( - DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config - ) { - exists(ParamNodeEx p | - revFlow(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) { - revFlowIn(call, arg, true, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(NodeEx out | - revFlow(out, toReturn, config) and - fwdFlowOutFromArg(call, out, config) - ) - } - - private predicate stateStepRev(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - additionalLocalStateStep(node1, state1, node2, state2, config) or - additionalJumpStateStep(node1, state1, node2, state2, config) - | - revFlow(node1, _, pragma[only_bind_into](config)) and - revFlow(node2, _, pragma[only_bind_into](config)) and - fwdFlowState(state1, pragma[only_bind_into](config)) and - fwdFlowState(state2, pragma[only_bind_into](config)) - ) - } - - additional predicate revFlowState(FlowState state, Configuration config) { - exists(NodeEx node | - sinkNode(node, state, config) and - revFlow(node, _, pragma[only_bind_into](config)) and - fwdFlowState(state, pragma[only_bind_into](config)) - ) - or - exists(FlowState state0 | - revFlowState(state0, config) and - stateStepRev(state, state0, config) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Content c | - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - revFlow(node2, pragma[only_bind_into](config)) and - store(node1, tc, node2, contentType, config) and - c = tc.getContent() and - exists(ap1) - ) - } - - pragma[nomagic] - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) { - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - read(n1, c, n2, pragma[only_bind_into](config)) and - revFlow(n2, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, config) and - exists(ap) - } - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, _, pragma[only_bind_into](config)) and - exists(state) and - exists(ap) - } - - private predicate throughFlowNodeCand(NodeEx node, Configuration config) { - revFlow(node, true, config) and - fwdFlow(node, true, config) and - not inBarrier(node, config) and - not outBarrier(node, config) - } - - /** Holds if flow may return from `callable`. */ - pragma[nomagic] - private predicate returnFlowCallableNodeCand( - DataFlowCallable callable, ReturnKindExt kind, Configuration config - ) { - exists(RetNodeEx ret | - throughFlowNodeCand(ret, config) and - callable = ret.getEnclosingCallable() and - kind = ret.getKind() - ) - } - - /** - * Holds if flow may enter through `p` and reach a return node making `p` a - * candidate for the origin of a summary. - */ - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(DataFlowCallable c, ReturnKindExt kind | - throughFlowNodeCand(p, config) and - returnFlowCallableNodeCand(c, kind, config) and - p.getEnclosingCallable() = c and - exists(ap) and - parameterFlowThroughAllowed(p, kind) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - throughFlowNodeCand(ret, config) and - kind = ret.getKind() and - exists(argAp) and - exists(ap) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, boolean toReturn | - revFlow(arg, toReturn, config) and - revFlowInToReturn(call, arg, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, config)) and - fields = count(Content f0 | fwdFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | fwdFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, config)) and - fields = count(Content f0 | revFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | revFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | revFlow(n, b, config)) - } - /* End: Stage 1 logic. */ -} - -pragma[noinline] -private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - localFlowStep(node1, node2, config) -} - -pragma[noinline] -private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - additionalLocalFlowStep(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCand1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config -) { - Stage1::revFlow(out, config) and - Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, Configuration config -) { - exists(ReturnPosition pos | - viableReturnPosOutNodeCand1(call, pos, out, config) and - pos = ret.getReturnPosition() and - kind = pos.getKind() and - Stage1::revFlow(ret, config) and - not outBarrier(ret, config) and - not inBarrier(out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCand1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config -) { - Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and - Stage1::revFlow(arg, config) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config -) { - viableParamArgNodeCand1(call, p, arg, config) and - Stage1::revFlow(p, config) and - not outBarrier(arg, config) and - not inBarrier(p, config) -} - -/** - * Gets the amount of forward branching on the origin of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int branch(NodeEx n1, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n1, _, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) - ) -} - -/** - * Gets the amount of backward branching on the target of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int join(NodeEx n2, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n, _, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) - ) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. The - * `allowsFieldFlow` flag indicates whether the branching is within the limit - * specified by the configuration. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, ret, kind, out, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(ret, pragma[only_bind_into](config)) and - j = join(out, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. The `allowsFieldFlow` flag indicates whether - * the branching is within the limit specified by the configuration. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config -) { - flowIntoCallNodeCand1(call, arg, p, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(arg, pragma[only_bind_into](config)) and - j = join(p, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -private signature module StageSig { - class Ap; - - predicate revFlow(NodeEx node, Configuration config); - - predicate revFlowAp(NodeEx node, Ap ap, Configuration config); - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config); - - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config); - - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config); - - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ); - - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ); - - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config); -} - -private module MkStage { - class ApApprox = PrevStage::Ap; - - signature module StageParam { - class Ap; - - class ApNil extends Ap; - - bindingset[result, ap] - ApApprox getApprox(Ap ap); - - ApNil getApNil(NodeEx node); - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail); - - /** - * An approximation of `Content` that corresponds to the precision level of - * `Ap`, such that the mappings from both `Ap` and `Content` to this type - * are functional. - */ - class ApHeadContent; - - ApHeadContent getHeadContent(Ap ap); - - ApHeadContent projectToHeadContent(Content c); - - class ApOption; - - ApOption apNone(); - - ApOption apSome(Ap ap); - - class Cc; - - class CcCall extends Cc; - - // TODO: member predicate on CcCall - predicate matchesCall(CcCall cc, DataFlowCall call); - - class CcNoCall extends Cc; - - Cc ccNone(); - - CcCall ccSomeCall(); - - class LocalCc; - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc); - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ); - - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config - ); - - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config - ); - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config); - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType); - } - - module Stage implements StageSig { - import Param - - /* Begin: Stage logic. */ - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - private predicate revFlowApAlias(NodeEx node, ApApprox apa, Configuration config) { - PrevStage::revFlowAp(node, apa, config) - } - - pragma[nomagic] - private predicate flowIntoCallApa( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa, - Configuration config - ) { - flowIntoCall(call, arg, p, allowsFieldFlow, config) and - PrevStage::revFlowAp(p, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(arg, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowOutOfCallApa( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - ApApprox apa, Configuration config - ) { - flowOutOfCall(call, ret, kind, out, allowsFieldFlow, config) and - PrevStage::revFlowAp(out, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(ret, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowThroughOutOfCall( - DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, - ApApprox argApa, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa, pragma[only_bind_into](config)) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) and - matchesCall(ccc, call) - ) - } - - /** - * Holds if `node` is reachable with access path `ap` from a source in the - * configuration `config`. - * - * The call context `cc` records whether the node is reached through an - * argument in a call, and if so, `summaryCtx` and `argAp` record the - * corresponding parameter position and access path of that argument, respectively. - */ - pragma[nomagic] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa, config) and - PrevStage::revFlow(node, state, apa, config) and - filter(node, state, ap, config) - } - - pragma[inline] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, cc, summaryCtx, argAp, ap, _, config) - } - - pragma[nomagic] - private predicate fwdFlow0( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - sourceNode(node, state, config) and - (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and - argAp = apNone() and - summaryCtx = TParamNodeNone() and - ap = getApNil(node) and - apa = getApprox(ap) - or - exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | - fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0, config) and - localCc = getLocalCc(mid, cc) - | - localStep(mid, state0, node, state, true, _, config, localCc) and - ap = ap0 and - apa = apa0 - or - localStep(mid, state0, node, state, false, ap, config, localCc) and - ap0 instanceof ApNil and - apa = getApprox(ap) - ) - or - exists(NodeEx mid | - fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa, pragma[only_bind_into](config)) and - jumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(mid, state, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(mid, state0, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStateStep(mid, state0, node, state, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - // store - exists(TypedContent tc, Ap ap0 | - fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp, config) and - ap = apCons(tc, ap0) and - apa = getApprox(ap) - ) - or - // read - exists(Ap ap0, Content c | - fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp, config) and - fwdFlowConsCand(ap0, c, ap, config) and - apa = getApprox(ap) - ) - or - // flow into a callable - fwdFlowIn(_, node, state, _, cc, _, _, ap, apa, config) and - if PrevStage::parameterMayFlowThrough(node, apa, config) - then ( - summaryCtx = TParamNodeSome(node.asNode()) and - argAp = apSome(ap) - ) else ( - summaryCtx = TParamNodeNone() and argAp = apNone() - ) - or - // flow out of a callable - exists( - DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, - DataFlowCallable inner - | - fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa, config) and - flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa, config) and - inner = ret.getEnclosingCallable() and - cc = getCallContextReturn(inner, call, innercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - or - // flow through a callable - exists( - DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, ApApprox innerArgApa - | - fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowStore( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(DataFlowType contentType, ApApprox apa1 | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1, config) and - PrevStage::storeStepCand(node1, apa1, tc, node2, contentType, config) and - typecheckStore(ap1, contentType) - ) - } - - /** - * Holds if forward flow with access path `tail` reaches a store of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(TypedContent tc | - fwdFlowStore(_, tail, tc, _, _, _, _, _, config) and - tc.getContent() = c and - cons = apCons(tc, tail) - ) - } - - pragma[nomagic] - private predicate readStepCand( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - PrevStage::readStepCand(node1, c, node2, config) and - apc = projectToHeadContent(c) - } - - bindingset[node1, apc] - pragma[inline_late] - private predicate readStepCand0( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - readStepCand(node1, apc, c, node2, config) - } - - pragma[nomagic] - private predicate fwdFlowRead( - Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(ApHeadContent apc | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap, config) and - apc = getHeadContent(ap) and - readStepCand0(node1, apc, c, node2, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowIn( - DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, - ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa, Configuration config - ) { - exists(ArgNodeEx arg, boolean allowsFieldFlow | - fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa, config) and - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowRetFromArg( - RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, ApApprox argApa, - Ap ap, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - fwdFlow(pragma[only_bind_into](ret), state, ccc, - TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), - pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa), - pragma[only_bind_into](config)) and - kind = ret.getKind() and - parameterFlowThroughAllowed(summaryCtx, kind) and - argApa = getApprox(argAp) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) - ) - } - - pragma[inline] - private predicate fwdFlowThrough0( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, - Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa, config) and - fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp, config) - } - - pragma[nomagic] - private predicate fwdFlowThrough( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa, - config) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` - * and data might flow through the target callable and back out at `call`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered( - DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, - ParamNodeEx p, Ap ap, Configuration config - ) { - exists(ApApprox apa | - fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(p, apa, config) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate storeStepFwd( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config - ) { - fwdFlowStore(node1, ap1, tc, node2, _, _, _, _, config) and - ap2 = apCons(tc, ap1) and - fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _, config) - } - - private predicate readStepFwd( - NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config - ) { - fwdFlowRead(ap1, c, n1, n2, _, _, _, _, config) and - fwdFlowConsCand(ap1, c, ap2, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough0( - DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, - ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, - innerArgApa, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough( - RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, - Ap ap, Configuration config - ) { - exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | - returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa, config) and - pos = ret.getReturnPosition() and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowThroughIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap, - Configuration config - ) { - exists(ApApprox argApa | - flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), - allowsFieldFlow, argApa, pragma[only_bind_into](config)) and - fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa, - pragma[only_bind_into](config)) and - returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap, - pragma[only_bind_into](config)) and - if allowsFieldFlow = false then argAp instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowIntoCallAp( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap, - Configuration config - ) { - exists(ApApprox apa | - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - fwdFlow(arg, _, _, _, _, ap, apa, config) - ) - } - - pragma[nomagic] - private predicate flowOutOfCallAp( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, - Ap ap, Configuration config - ) { - exists(ApApprox apa | - flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa, config) and - fwdFlow(ret, _, _, _, _, ap, apa, config) and - pos = ret.getReturnPosition() - ) - } - - /** - * Holds if `node` with access path `ap` is part of a path from a source to a - * sink in the configuration `config`. - * - * The parameter `returnCtx` records whether (and how) the node must be returned - * from the enclosing callable in order to reach a sink, and if so, `returnAp` - * records the access path of the returned value. - */ - pragma[nomagic] - additional predicate revFlow( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - revFlow0(node, state, returnCtx, returnAp, ap, config) and - fwdFlow(node, state, _, _, _, ap, config) - } - - pragma[nomagic] - private predicate revFlow0( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, _, _, _, ap, config) and - sinkNode(node, state, config) and - ( - if hasSinkCallCtx(config) - then returnCtx = TReturnCtxNoFlowThrough() - else returnCtx = TReturnCtxNone() - ) and - returnAp = apNone() and - ap instanceof ApNil - or - exists(NodeEx mid, FlowState state0 | - localStep(node, state, mid, state0, true, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, ap, config) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap, pragma[only_bind_into](config)) and - localStep(node, pragma[only_bind_into](state), mid, state0, false, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, nil, pragma[only_bind_into](config)) and - ap instanceof ApNil - ) - or - exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, state, _, _, ap, config) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStep(node, mid, config) and - revFlow(pragma[only_bind_into](mid), state, _, _, nil, pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStateStep(node, state, mid, state0, config) and - revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil, - pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - // store - exists(Ap ap0, Content c | - revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp, config) and - revFlowConsCand(ap0, c, ap, config) - ) - or - // read - exists(NodeEx mid, Ap ap0 | - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - readStepFwd(node, ap, _, mid, ap0, config) - ) - or - // flow into a callable - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, state, TReturnCtxNone(), returnAp, ap, config) and - flowIntoCallAp(_, node, p, allowsFieldFlow, ap, config) and - (if allowsFieldFlow = false then ap instanceof ApNil else any()) and - returnCtx = TReturnCtxNone() - ) - or - // flow through a callable - exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, node, p, _, ap, innerReturnAp, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(_, node, pos, state, _, _, ap, config) and - if returnFlowsThrough(node, pos, state, _, _, _, ap, config) - then ( - returnCtx = TReturnCtxMaybeFlowThrough(pos) and - returnAp = apSome(ap) - ) else ( - returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() - ) - ) - } - - pragma[nomagic] - private predicate revFlowStore( - Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, - ReturnCtx returnCtx, ApOption returnAp, Configuration config - ) { - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - storeStepFwd(node, ap, tc, mid, ap0, config) and - tc.getContent() = c - } - - /** - * Holds if reverse flow with access path `tail` reaches a read of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(NodeEx mid, Ap tail0 | - revFlow(mid, _, _, _, tail, config) and - tail = pragma[only_bind_into](tail0) and - readStepFwd(_, cons, c, mid, tail0, config) - ) - } - - pragma[nomagic] - private predicate revFlowOut( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, - ApOption returnAp, Ap ap, Configuration config - ) { - exists(NodeEx out, boolean allowsFieldFlow | - revFlow(out, state, returnCtx, returnAp, ap, config) and - flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate revFlowParamToReturn( - ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap, Configuration config - ) { - revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), - pragma[only_bind_into](ap), pragma[only_bind_into](config)) and - parameterFlowThroughAllowed(p, pos.getKind()) and - PrevStage::parameterMayFlowThrough(p, getApprox(ap), config) - } - - pragma[nomagic] - private predicate revFlowThrough( - DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, - ApOption returnAp, Ap ap, Ap innerReturnAp, Configuration config - ) { - revFlowParamToReturn(p, state, pos, innerReturnAp, ap, config) and - revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned( - DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap, - Configuration config - ) { - exists(RetNodeEx ret, FlowState state, CcCall ccc | - revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap, config) and - returnFlowsThrough(ret, pos, state, ccc, _, _, ap, config) and - matchesCall(ccc, call) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Ap ap2, Content c | - PrevStage::storeStepCand(node1, _, tc, node2, contentType, config) and - revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _, config) and - revFlowConsCand(ap2, c, ap1, config) - ) - } - - predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(Ap ap1, Ap ap2 | - revFlow(node2, _, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and - readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _, - pragma[only_bind_into](config)) - ) - } - - additional predicate revFlow(NodeEx node, FlowState state, Configuration config) { - revFlow(node, state, _, _, _, config) - } - - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, _, _, ap, config) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, _, _, _, ap, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, Configuration config) { - revFlow(node, _, _, _, _, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, ap, config) - } - - private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepFwd(_, ap, tc, _, _, config) - } - - private predicate revConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepCand(_, ap, tc, _, _, config) - } - - private predicate validAp(Ap ap, Configuration config) { - revFlow(_, _, _, _, ap, config) and ap instanceof ApNil - or - exists(TypedContent head, Ap tail | - consCand(head, tail, config) and - ap = apCons(head, tail) - ) - } - - additional predicate consCand(TypedContent tc, Ap ap, Configuration config) { - revConsCand(tc, ap, config) and - validAp(ap, config) - } - - pragma[nomagic] - private predicate parameterFlowsThroughRev( - ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp, Configuration config - ) { - revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap, config) and - parameterFlowThroughAllowed(p, pos.getKind()) - } - - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(ReturnPosition pos | - returnFlowsThrough(_, pos, _, _, p, ap, _, config) and - parameterFlowsThroughRev(p, ap, pos, _, config) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - exists(ParamNodeEx p, ReturnPosition pos | - returnFlowsThrough(ret, pos, _, _, p, argAp, ap, config) and - parameterFlowsThroughRev(p, argAp, pos, ap, config) and - kind = pos.getKind() - ) - } - - pragma[nomagic] - private predicate revFlowThroughArg( - DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, - Ap ap, Configuration config - ) { - exists(ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp, config) - ) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | - revFlow(arg, state, returnCtx, returnAp, ap, config) and - revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, config)) and - fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and - states = count(FlowState state | fwdFlow(_, state, _, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | - fwdFlow(n, state, cc, summaryCtx, argAp, ap, config) - ) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, _, _, _, config)) and - fields = count(TypedContent f0 | consCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and - states = count(FlowState state | revFlow(_, state, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | - revFlow(n, state, returnCtx, retAp, ap, config) - ) - } - /* End: Stage logic. */ - } -} - -private module BooleanCallContext { - class Cc extends boolean { - Cc() { this in [true, false] } - } - - class CcCall extends Cc { - CcCall() { this = true } - } - - /** Holds if the call context may be `call`. */ - predicate matchesCall(CcCall cc, DataFlowCall call) { any() } - - class CcNoCall extends Cc { - CcNoCall() { this = false } - } - - Cc ccNone() { result = false } - - CcCall ccSomeCall() { result = true } - - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } -} - -private module Level1CallContext { - class Cc = CallContext; - - class CcCall = CallContextCall; - - pragma[inline] - predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } - - class CcNoCall = CallContextNoCall; - - Cc ccNone() { result instanceof CallContextAny } - - CcCall ccSomeCall() { result instanceof CallContextSomeCall } - - module NoLocalCallContext { - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSiteDispatch(call, c) - then result = TSpecificCall(call) - else result = TSomeCall() - } - } - - module LocalCallContext { - class LocalCc = LocalCallContext; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { - result = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - node.getEnclosingCallable()) - } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() - } - } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { - checkCallContextReturn(innercc, c, call) and - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() - } -} - -private module Stage2Param implements MkStage::StageParam { - private module PrevStage = Stage1; - - class Ap extends boolean { - Ap() { this in [true, false] } - } - - class ApNil extends Ap { - ApNil() { this = false } - } - - bindingset[result, ap] - PrevStage::Ap getApprox(Ap ap) { any() } - - ApNil getApNil(NodeEx node) { Stage1::revFlow(node, _) and exists(result) } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } - - class ApHeadContent = Unit; - - pragma[inline] - ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } - - ApHeadContent projectToHeadContent(Content c) { any() } - - class ApOption = BooleanOption; - - ApOption apNone() { result = TBooleanNone() } - - ApOption apSome(Ap ap) { result = TBooleanSome(ap) } - - import Level1CallContext - import NoLocalCallContext - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - ( - preservesValue = true and - localFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalStateStep(node1, state1, node2, state2, config) - ) and - exists(ap) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand1/6; - - predicate flowIntoCall = flowIntoCallNodeCand1/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - expectsContentEx(node, c) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - PrevStage::revFlowState(state, pragma[only_bind_into](config)) and - exists(ap) and - not stateBarrier(node, state, config) and - ( - notExpectsContent(node) - or - ap = true and - expectsContentCand(node, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage2 implements StageSig { - import MkStage::Stage -} - -pragma[nomagic] -private predicate flowOutOfCallNodeCand2( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -pragma[nomagic] -private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -private module LocalFlowBigStep { - /** - * A node where some checking is required, and hence the big-step relation - * is not allowed to step over. - */ - private class FlowCheckNode extends NodeEx { - FlowCheckNode() { - castNode(this.asNode()) or - clearsContentCached(this.asNode(), _) or - expectsContentCached(this.asNode(), _) - } - } - - /** - * Holds if `node` can be the first node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowEntry(NodeEx node, FlowState state, Configuration config) { - Stage2::revFlow(node, state, config) and - ( - sourceNode(node, state, config) - or - jumpStep(_, node, config) - or - additionalJumpStep(_, node, config) - or - additionalJumpStateStep(_, _, node, state, config) - or - node instanceof ParamNodeEx - or - node.asNode() instanceof OutNodeExt - or - Stage2::storeStepCand(_, _, _, node, _, config) - or - Stage2::readStepCand(_, _, node, config) - or - node instanceof FlowCheckNode - or - exists(FlowState s | - additionalLocalStateStep(_, s, node, state, config) and - s != state - ) - ) - } - - /** - * Holds if `node` can be the last node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowExit(NodeEx node, FlowState state, Configuration config) { - exists(NodeEx next | Stage2::revFlow(next, state, config) | - jumpStep(node, next, config) or - additionalJumpStep(node, next, config) or - flowIntoCallNodeCand2(_, node, next, _, config) or - flowOutOfCallNodeCand2(_, node, _, next, _, config) or - Stage2::storeStepCand(node, _, _, next, _, config) or - Stage2::readStepCand(node, _, next, config) - ) - or - exists(NodeEx next, FlowState s | Stage2::revFlow(next, s, config) | - additionalJumpStateStep(node, state, next, s, config) - or - additionalLocalStateStep(node, state, next, s, config) and - s != state - ) - or - Stage2::revFlow(node, state, config) and - node instanceof FlowCheckNode - or - sinkNode(node, state, config) - } - - pragma[noinline] - private predicate additionalLocalFlowStepNodeCand2( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, Configuration config - ) { - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 and - Stage2::revFlow(node1, pragma[only_bind_into](state1), false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false, - pragma[only_bind_into](config)) - or - additionalLocalStateStep(node1, state1, node2, state2, config) and - Stage2::revFlow(node1, state1, false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, state2, false, pragma[only_bind_into](config)) - } - - /** - * Holds if the local path from `node1` to `node2` is a prefix of a maximal - * subsequence of local flow steps in a dataflow path. - * - * This is the transitive closure of `[additional]localFlowStep` beginning - * at `localFlowEntry`. - */ - pragma[nomagic] - private predicate localFlowStepPlus( - NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, - Configuration config, LocalCallContext cc - ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and - ( - localFlowEntry(node1, pragma[only_bind_into](state), pragma[only_bind_into](config)) and - ( - localFlowStepNodeCand1(node1, node2, config) and - preservesValue = true and - t = node1.getDataFlowType() and // irrelevant dummy value - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - or - additionalLocalFlowStepNodeCand2(node1, state, node2, state, config) and - preservesValue = false and - t = node2.getDataFlowType() - ) and - node1 != node2 and - cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) - or - exists(NodeEx mid | - localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, - pragma[only_bind_into](config), cc) and - localFlowStepNodeCand1(mid, node2, config) and - not mid instanceof FlowCheckNode and - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - ) - or - exists(NodeEx mid | - localFlowStepPlus(node1, state, mid, _, _, pragma[only_bind_into](config), cc) and - additionalLocalFlowStepNodeCand2(mid, state, node2, state, config) and - not mid instanceof FlowCheckNode and - preservesValue = false and - t = node2.getDataFlowType() - ) - ) - } - - /** - * Holds if `node1` can step to `node2` in one or more local steps and this - * path can occur as a maximal subsequence of local steps in a dataflow path. - */ - pragma[nomagic] - predicate localFlowBigStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - DataFlowType t, Configuration config, LocalCallContext callContext - ) { - localFlowStepPlus(node1, state1, node2, preservesValue, t, config, callContext) and - localFlowExit(node2, state1, config) and - state1 = state2 - or - additionalLocalFlowStepNodeCand2(node1, state1, node2, state2, config) and - state1 != state2 and - preservesValue = false and - t = node2.getDataFlowType() and - callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) - } -} - -private import LocalFlowBigStep - -private module Stage3Param implements MkStage::StageParam { - private module PrevStage = Stage2; - - class Ap = ApproxAccessPathFront; - - class ApNil = ApproxAccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } - - class ApHeadContent = ContentApprox; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - predicate projectToHeadContent = getContentApprox/1; - - class ApOption = ApproxAccessPathFrontOption; - - ApOption apNone() { result = TApproxAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } - - import BooleanCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApproxAccessPathFrontNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand2/6; - - predicate flowIntoCall = flowIntoCallNodeCand2/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getAHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage3 implements StageSig { - import MkStage::Stage -} - -private module Stage4Param implements MkStage::StageParam { - private module PrevStage = Stage3; - - class Ap = AccessPathFront; - - class ApNil = AccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathFrontOption; - - ApOption apNone() { result = TAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } - - import BooleanCallContext - - pragma[nomagic] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) and - exists(lcc) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clearSet(NodeEx node, ContentSet c, Configuration config) { - PrevStage::revFlow(node, config) and - clearsContentCached(node.asNode(), c) - } - - pragma[nomagic] - private predicate clearContent(NodeEx node, Content c, Configuration config) { - exists(ContentSet cs | - PrevStage::readStepCand(_, pragma[only_bind_into](c), _, pragma[only_bind_into](config)) and - c = cs.getAReadContent() and - clearSet(node, cs, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clear(NodeEx node, Ap ap, Configuration config) { - clearContent(node, ap.getHead().getContent(), config) - } - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - not clear(node, ap, config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage4 implements StageSig { - import MkStage::Stage -} - -/** - * Holds if `argApf` is recorded as the summary context for flow reaching `node` - * and remains relevant for the following pruning stage. - */ -private predicate flowCandSummaryCtx( - NodeEx node, FlowState state, AccessPathFront argApf, Configuration config -) { - exists(AccessPathFront apf | - Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf, config) and - Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf, - config) - ) -} - -/** - * Holds if a length 2 access path approximation with the head `tc` is expected - * to be expensive. - */ -private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { - exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf, config)) and - nodes = - strictcount(NodeEx n, FlowState state | - Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - or - flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - ) and - accessPathApproxCostLimits(apLimit, tupleLimit) and - apLimit < tails and - tupleLimit < (tails - 1) * nodes and - not tc.forceHighPrecision() - ) -} - -private newtype TAccessPathApprox = - TNil(DataFlowType t) or - TConsNil(TypedContent tc, DataFlowType t) { - Stage4::consCand(tc, TFrontNil(t), _) and - not expensiveLen2unfolding(tc, _) - } or - TConsCons(TypedContent tc1, TypedContent tc2, int len) { - Stage4::consCand(tc1, TFrontHead(tc2), _) and - len in [2 .. accessPathLimit()] and - not expensiveLen2unfolding(tc1, _) - } or - TCons1(TypedContent tc, int len) { - len in [1 .. accessPathLimit()] and - expensiveLen2unfolding(tc, _) - } - -/** - * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only - * the first two elements of the list and its length are tracked. If data flows - * from a source to a given node with a given `AccessPathApprox`, this indicates - * the sequence of dereference operations needed to get from the value in the node - * to the tracked object. The final type indicates the type of the tracked object. - */ -abstract private class AccessPathApprox extends TAccessPathApprox { - abstract string toString(); - - abstract TypedContent getHead(); - - abstract int len(); - - abstract DataFlowType getType(); - - abstract AccessPathFront getFront(); - - /** Gets the access path obtained by popping `head` from this path, if any. */ - abstract AccessPathApprox pop(TypedContent head); -} - -private class AccessPathApproxNil extends AccessPathApprox, TNil { - private DataFlowType t; - - AccessPathApproxNil() { this = TNil(t) } - - override string toString() { result = concat(": " + ppReprType(t)) } - - override TypedContent getHead() { none() } - - override int len() { result = 0 } - - override DataFlowType getType() { result = t } - - override AccessPathFront getFront() { result = TFrontNil(t) } - - override AccessPathApprox pop(TypedContent head) { none() } -} - -abstract private class AccessPathApproxCons extends AccessPathApprox { } - -private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { - private TypedContent tc; - private DataFlowType t; - - AccessPathApproxConsNil() { this = TConsNil(tc, t) } - - override string toString() { - // The `concat` becomes "" if `ppReprType` has no result. - result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) - } - - override TypedContent getHead() { result = tc } - - override int len() { result = 1 } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } -} - -private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { - private TypedContent tc1; - private TypedContent tc2; - private int len; - - AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - - override string toString() { - if len = 2 - then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" - else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc1 } - - override int len() { result = len } - - override DataFlowType getType() { result = tc1.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc1) } - - override AccessPathApprox pop(TypedContent head) { - head = tc1 and - ( - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - } -} - -private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { - private TypedContent tc; - private int len; - - AccessPathApproxCons1() { this = TCons1(tc, len) } - - override string toString() { - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc } - - override int len() { result = len } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { - head = tc and - ( - exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2), _) | - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - or - exists(DataFlowType t | - len = 1 and - Stage4::consCand(tc, TFrontNil(t), _) and - result = TNil(t) - ) - ) - } -} - -/** Gets the access path obtained by popping `tc` from `ap`, if any. */ -private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } - -/** Gets the access path obtained by pushing `tc` onto `ap`. */ -private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } - -private newtype TAccessPathApproxOption = - TAccessPathApproxNone() or - TAccessPathApproxSome(AccessPathApprox apa) - -private class AccessPathApproxOption extends TAccessPathApproxOption { - string toString() { - this = TAccessPathApproxNone() and result = "" - or - this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) - } -} - -private module Stage5Param implements MkStage::StageParam { - private module PrevStage = Stage4; - - class Ap = AccessPathApprox; - - class ApNil = AccessPathApproxNil; - - pragma[nomagic] - PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathApproxOption; - - ApOption apNone() { result = TAccessPathApproxNone() } - - ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } - - import Level1CallContext - import LocalCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, lcc) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { any() } - - // Type checking is not necessary here as it has already been done in stage 3. - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage5 = MkStage::Stage; - -bindingset[conf, result] -private Configuration unbindConf(Configuration conf) { - exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c)) -} - -pragma[nomagic] -private predicate nodeMayUseSummary0( - NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(AccessPathApprox apa0 | - Stage5::parameterMayFlowThrough(p, _, _) and - Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0, config) and - Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), - TAccessPathApproxSome(apa), apa0, config) - ) -} - -pragma[nomagic] -private predicate nodeMayUseSummary( - NodeEx n, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::parameterMayFlowThrough(p, apa, config) and - nodeMayUseSummary0(n, p, state, apa, config) - ) -} - -private newtype TSummaryCtx = - TSummaryCtxNone() or - TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { - exists(Configuration config | - Stage5::parameterMayFlowThrough(p, ap.getApprox(), config) and - Stage5::revFlow(p, state, _, config) - ) - } - -/** - * A context for generating flow summaries. This represents flow entry through - * a specific parameter with an access path of a specific shape. - * - * Summaries are only created for parameters that may flow through. - */ -abstract private class SummaryCtx extends TSummaryCtx { - abstract string toString(); -} - -/** A summary context from which no flow summary can be generated. */ -private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { - override string toString() { result = "" } -} - -/** A summary context from which a flow summary can be generated. */ -private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParamNodeEx p; - private FlowState s; - private AccessPath ap; - - SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } - - ParameterPosition getParameterPos() { p.isParameterOf(_, result) } - - ParamNodeEx getParamNode() { result = p } - - override string toString() { result = p + ": " + ap } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** - * Gets the number of length 2 access path approximations that correspond to `apa`. - */ -private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { - exists(TypedContent tc, int len | - tc = apa.getHead() and - len = apa.len() and - result = - strictcount(AccessPathFront apf | - Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), - config) - ) - ) -} - -private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = - strictcount(NodeEx n, FlowState state | - Stage5::revFlow(n, state, apa, config) or nodeMayUseSummary(n, state, apa, config) - ) -} - -/** - * Holds if a length 2 access path approximation matching `apa` is expected - * to be expensive. - */ -private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = count1to2unfold(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - apLimit < aps and - tupleLimit < (aps - 1) * nodes - ) -} - -private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { - exists(TypedContent head | - apa.pop(head) = result and - Stage5::consCand(head, result, config) - ) -} - -/** - * Holds with `unfold = false` if a precise head-tail representation of `apa` is - * expected to be expensive. Holds with `unfold = true` otherwise. - */ -private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { - if apa.getHead().forceHighPrecision() - then unfold = true - else - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = countPotentialAps(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true - ) -} - -/** - * Gets the number of `AccessPath`s that correspond to `apa`. - */ -pragma[assume_small_delta] -private int countAps(AccessPathApprox apa, Configuration config) { - evalUnfold(apa, false, config) and - result = 1 and - (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) - or - evalUnfold(apa, false, config) and - result = count1to2unfold(apa, config) and - not expensiveLen1to2unfolding(apa, config) - or - evalUnfold(apa, true, config) and - result = countPotentialAps(apa, config) -} - -/** - * Gets the number of `AccessPath`s that would correspond to `apa` assuming - * that it is expanded to a precise head-tail representation. - */ -language[monotonicAggregates] -pragma[assume_small_delta] -private int countPotentialAps(AccessPathApprox apa, Configuration config) { - apa instanceof AccessPathApproxNil and result = 1 - or - result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) -} - -private newtype TAccessPath = - TAccessPathNil(DataFlowType t) or - TAccessPathCons(TypedContent head, AccessPath tail) { - exists(AccessPathApproxCons apa | - not evalUnfold(apa, false, _) and - head = apa.getHead() and - tail.getApprox() = getATail(apa, _) - ) - } or - TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - not expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head1 = apa.getHead() and - head2 = getATail(apa, _).getHead() - ) - } or - TAccessPathCons1(TypedContent head, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head = apa.getHead() - ) - } - -private newtype TPathNode = - pragma[assume_small_delta] - TPathNodeMid( - NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config - ) { - // A PathNode is introduced by a source ... - Stage5::revFlow(node, state, config) and - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - or - // ... or a step from an existing PathNode to another node. - exists(PathNodeMid mid | - pathStep(mid, node, state, cc, sc, ap) and - pragma[only_bind_into](config) = mid.getConfiguration() and - Stage5::revFlow(node, state, ap.getApprox(), pragma[only_bind_into](config)) - ) - } or - TPathNodeSink(NodeEx node, FlowState state, Configuration config) { - exists(PathNodeMid sink | - sink.isAtSink() and - node = sink.getNodeEx() and - state = sink.getState() and - config = sink.getConfiguration() - ) - } or - TPathNodeSourceGroup(string sourceGroup, Configuration config) { - exists(PathNodeImpl source | - sourceGroup = source.getSourceGroup() and - config = source.getConfiguration() - ) - } or - TPathNodeSinkGroup(string sinkGroup, Configuration config) { - exists(PathNodeSink sink | - sinkGroup = sink.getSinkGroup() and - config = sink.getConfiguration() - ) - } - -/** - * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a - * source to a given node with a given `AccessPath`, this indicates the sequence - * of dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ -private class AccessPath extends TAccessPath { - /** Gets the head of this access path, if any. */ - abstract TypedContent getHead(); - - /** Gets the tail of this access path, if any. */ - abstract AccessPath getTail(); - - /** Gets the front of this access path. */ - abstract AccessPathFront getFront(); - - /** Gets the approximation of this access path. */ - abstract AccessPathApprox getApprox(); - - /** Gets the length of this access path. */ - abstract int length(); - - /** Gets a textual representation of this access path. */ - abstract string toString(); - - /** Gets the access path obtained by popping `tc` from this access path, if any. */ - final AccessPath pop(TypedContent tc) { - result = this.getTail() and - tc = this.getHead() - } - - /** Gets the access path obtained by pushing `tc` onto this access path. */ - final AccessPath push(TypedContent tc) { this = result.pop(tc) } -} - -private class AccessPathNil extends AccessPath, TAccessPathNil { - private DataFlowType t; - - AccessPathNil() { this = TAccessPathNil(t) } - - DataFlowType getType() { result = t } - - override TypedContent getHead() { none() } - - override AccessPath getTail() { none() } - - override AccessPathFrontNil getFront() { result = TFrontNil(t) } - - override AccessPathApproxNil getApprox() { result = TNil(t) } - - override int length() { result = 0 } - - override string toString() { result = concat(": " + ppReprType(t)) } -} - -private class AccessPathCons extends AccessPath, TAccessPathCons { - private TypedContent head; - private AccessPath tail; - - AccessPathCons() { this = TAccessPathCons(head, tail) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { result = tail } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - pragma[assume_small_delta] - override AccessPathApproxCons getApprox() { - result = TConsNil(head, tail.(AccessPathNil).getType()) - or - result = TConsCons(head, tail.getHead(), this.length()) - or - result = TCons1(head, this.length()) - } - - pragma[assume_small_delta] - override int length() { result = 1 + tail.length() } - - private string toStringImpl(boolean needsSuffix) { - exists(DataFlowType t | - tail = TAccessPathNil(t) and - needsSuffix = false and - result = head.toString() + "]" + concat(" : " + ppReprType(t)) - ) - or - result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) - or - exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | - result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true - or - result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false - ) - or - exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | - result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true - or - result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false - ) - } - - override string toString() { - result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" - or - result = "[" + this.toStringImpl(false) - } -} - -private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { - private TypedContent head1; - private TypedContent head2; - private int len; - - AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } - - override TypedContent getHead() { result = head1 } - - override AccessPath getTail() { - Stage5::consCand(head1, result.getApprox(), _) and - result.getHead() = head2 and - result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head1) } - - override AccessPathApproxCons getApprox() { - result = TConsCons(head1, head2, len) or - result = TCons1(head1, len) - } - - override int length() { result = len } - - override string toString() { - if len = 2 - then result = "[" + head1.toString() + ", " + head2.toString() + "]" - else - result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" - } -} - -private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { - private TypedContent head; - private int len; - - AccessPathCons1() { this = TAccessPathCons1(head, len) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { - Stage5::consCand(head, result.getApprox(), _) and result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - - override int length() { result = len } - - override string toString() { - if len = 1 - then result = "[" + head.toString() + "]" - else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" - } -} - -abstract private class PathNodeImpl extends TPathNode { - /** Gets the `FlowState` of this node. */ - abstract FlowState getState(); - - /** Gets the associated configuration. */ - abstract Configuration getConfiguration(); - - /** Holds if this node is a source. */ - abstract predicate isSource(); - - abstract PathNodeImpl getASuccessorImpl(); - - private PathNodeImpl getASuccessorIfHidden() { - this.isHidden() and - result = this.getASuccessorImpl() - } - - pragma[nomagic] - private PathNodeImpl getANonHiddenSuccessor0() { - result = this.getASuccessorIfHidden*() and - not result.isHidden() - } - - final PathNodeImpl getANonHiddenSuccessor() { - result = this.getASuccessorImpl().getANonHiddenSuccessor0() and - not this.isHidden() - } - - abstract NodeEx getNodeEx(); - - predicate isHidden() { - not this.getConfiguration().includeHiddenNodes() and - ( - hiddenNode(this.getNodeEx().asNode()) and - not this.isSource() and - not this instanceof PathNodeSink - or - this.getNodeEx() instanceof TNodeImplicitRead - ) - } - - string getSourceGroup() { - this.isSource() and - this.getConfiguration().sourceGrouping(this.getNodeEx().asNode(), result) + not singleConfiguration() and + getConfig(state1).isAdditionalFlowStep(node1, node2) and + state2 = state1 } - predicate isFlowSource() { - this.isSource() and not exists(this.getSourceGroup()) - or - this instanceof PathNodeSourceGroup + predicate allowImplicitRead(Node node, ContentSet c) { + any(Configuration config).allowImplicitRead(node, c) } - predicate isFlowSink() { - this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or - this instanceof PathNodeSinkGroup - } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } - private string ppAp() { - this instanceof PathNodeSink and result = "" - or - exists(string s | s = this.(PathNodeMid).getAp().toString() | - if s = "" then result = "" else result = " " + s - ) - } + FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } - private string ppCtx() { - this instanceof PathNodeSink and result = "" - or - result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + predicate sourceGrouping(Node source, string sourceGroup) { + any(Configuration config).sourceGrouping(source, sourceGroup) } - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + predicate sinkGrouping(Node sink, string sinkGroup) { + any(Configuration config).sinkGrouping(sink, sinkGroup) } -} - -/** Holds if `n` can reach a sink. */ -private predicate directReach(PathNodeImpl n) { - n instanceof PathNodeSink or - n instanceof PathNodeSinkGroup or - directReach(n.getANonHiddenSuccessor()) -} - -/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ -private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } -/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { - n1.getANonHiddenSuccessor() = n2 and directReach(n2) + predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() } } -private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) +private import Impl as I +import I /** * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. */ -class PathNode instanceof PathNodeImpl { - PathNode() { reach(this) } - +class PathNode instanceof I::PathNode { /** Gets a textual representation of this element. */ final string toString() { result = super.toString() } @@ -3406,1548 +358,39 @@ class PathNode instanceof PathNodeImpl { } /** Gets the underlying `Node`. */ - final Node getNode() { super.getNodeEx().projectToNode() = result } + final Node getNode() { result = super.getNode() } /** Gets the `FlowState` of this node. */ - final FlowState getState() { result = super.getState() } + final FlowState getState() { result = getState(super.getState()) } /** Gets the associated configuration. */ - final Configuration getConfiguration() { result = super.getConfiguration() } + final Configuration getConfiguration() { result = getConfig(super.getState()) } /** Gets a successor of this node, if any. */ - final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } + final PathNode getASuccessor() { result = super.getASuccessor() } /** Holds if this node is a source. */ final predicate isSource() { super.isSource() } /** Holds if this node is a grouping of source nodes. */ - final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group, _) } + final predicate isSourceGroup(string group) { super.isSourceGroup(group) } /** Holds if this node is a grouping of sink nodes. */ - final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group, _) } + final predicate isSinkGroup(string group) { super.isSinkGroup(group) } } -/** - * Provides the query predicates needed to include a graph in a path-problem query. - */ -module PathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } - - /** Holds if `n` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode n, string key, string val) { - key = "semmle.label" and val = n.toString() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { - Subpaths::subpaths(arg, par, ret, out) - } -} - -/** - * An intermediate flow graph node. This is a triple consisting of a `Node`, - * a `CallContext`, and a `Configuration`. - */ -private class PathNodeMid extends PathNodeImpl, TPathNodeMid { - NodeEx node; - FlowState state; - CallContext cc; - SummaryCtx sc; - AccessPath ap; - Configuration config; - - PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - SummaryCtx getSummaryCtx() { result = sc } - - AccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - private PathNodeMid getSuccMid() { - pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx(), result.getAp()) and - result.getConfiguration() = unbindConf(this.getConfiguration()) - } - - override PathNodeImpl getASuccessorImpl() { - // an intermediate step to another intermediate node - result = this.getSuccMid() - or - // a final step to a sink - result = this.getSuccMid().projectToSink() - } - - override predicate isSource() { - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - } - - predicate isAtSink() { - sinkNode(node, state, config) and - ap instanceof AccessPathNil and - if hasSinkCallCtx(config) - then - // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` - // is exactly what we need to check. This also implies - // `sc instanceof SummaryCtxNone`. - // For `FeatureEqualSourceSinkCallContext` the initial call context was - // set to `CallContextSomeCall` and jumps are disallowed, so - // `cc instanceof CallContextNoCall` never holds. On the other hand, - // in this case there's never any need to enter a call except to identify - // a summary, so the condition in `pathIntoCallable` enforces this, which - // means that `sc instanceof SummaryCtxNone` holds if and only if we are - // in the call context of the source. - sc instanceof SummaryCtxNone or - cc instanceof CallContextNoCall - else any() - } - - PathNodeSink projectToSink() { - this.isAtSink() and - result.getNodeEx() = node and - result.getState() = state and - result.getConfiguration() = unbindConf(config) - } -} - -/** - * A flow graph node corresponding to a sink. This is disjoint from the - * intermediate nodes in order to uniquely correspond to a given sink by - * excluding the `CallContext`. - */ -private class PathNodeSink extends PathNodeImpl, TPathNodeSink { - NodeEx node; - FlowState state; - Configuration config; - - PathNodeSink() { this = TPathNodeSink(node, state, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result = TPathNodeSinkGroup(this.getSinkGroup(), config) - } - - override predicate isSource() { sourceNode(node, state, config) } - - string getSinkGroup() { config.sinkGrouping(node.asNode(), result) } -} - -private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { - string sourceGroup; - Configuration config; - - PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result.getSourceGroup() = sourceGroup and - result.getConfiguration() = config - } - - override predicate isSource() { none() } - - override string toString() { result = sourceGroup } - - override 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 - } -} - -private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { - string sinkGroup; - Configuration config; - - PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { none() } - - override predicate isSource() { none() } - - override string toString() { result = sinkGroup } - - override 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 - } -} - -private predicate pathNode( - PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, - Configuration conf, LocalCallContext localCC -) { - midnode = mid.getNodeEx() and - state = mid.getState() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - midnode.getEnclosingCallable()) and - ap = mid.getAp() -} - -/** - * Holds if data may flow from `mid` to `node`. The last step in or out of - * a callable is recorded by `cc`. - */ -pragma[assume_small_delta] -pragma[nomagic] -private predicate pathStep( - PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap -) { - exists(NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC | - pathNode(mid, midnode, state0, cc, sc, ap, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, true, _, conf, localCC) - ) - or - exists( - AccessPath ap0, NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC - | - pathNode(mid, midnode, state0, cc, sc, ap0, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), conf, - localCC) and - ap0 instanceof AccessPathNil - ) - or - jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - ap = mid.getAp() - or - additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, mid.getConfiguration()) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - pathIntoCallable(mid, node, state, _, cc, sc, _, _) and ap = mid.getAp() - or - pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone - or - pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() -} - -pragma[nomagic] -private predicate pathReadStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - tc = ap0.getHead() and - Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -pragma[nomagic] -private predicate pathStoreStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -private predicate pathOutOfCallable0( - PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa, - Configuration config -) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - apa = mid.getAp().getApprox() and - config = mid.getConfiguration() -} - -pragma[nomagic] -private predicate pathOutOfCallable1( - PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPathApprox apa, Configuration config -) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - pathOutOfCallable0(mid, pos, state, innercc, apa, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() +private predicate hasFlow(Node source, Node sink, Configuration config) { + exists(PathNode source0, PathNode sink0 | + hasFlowPath(source0, sink0, config) and + source0.getNode() = source and + sink0.getNode() = sink ) } -pragma[noinline] -private NodeEx getAnOutNodeFlow( - ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config -) { - result.asNode() = kind.getAnOutNode(call) and - Stage5::revFlow(result, _, apa, config) +private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) { + hasFlowPath(source, sink) and source.getConfiguration() = config } -/** - * Holds if data may flow from `mid` to `out`. The last step of this path - * is a return from a callable and is recorded by `cc`, if needed. - */ -pragma[noinline] -private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { - exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | - pathOutOfCallable1(mid, call, kind, state, cc, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} +private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) } -/** - * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. - */ -pragma[noinline] -private predicate pathIntoArg( - PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(ArgNodeEx arg, ArgumentPosition apos | - pathNode(mid, arg, state, cc, _, ap, config, _) and - arg.asNode().(ArgNode).argumentOf(call, apos) and - apa = ap.getApprox() and - parameterMatch(ppos, apos) - ) -} - -pragma[nomagic] -private predicate parameterCand( - DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::revFlow(p, _, apa, config) and - p.isParameterOf(callable, pos) - ) -} - -pragma[nomagic] -private predicate pathIntoCallable0( - PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, AccessPath ap, Configuration config -) { - exists(AccessPathApprox apa | - pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - callable = resolveCall(call, outercc) and - parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa), - pragma[only_bind_into](config)) - ) -} - -/** - * Holds if data may flow from `mid` to `p` through `call`. The contexts - * before and after entering the callable are `outercc` and `innercc`, - * respectively. - */ -pragma[nomagic] -private predicate pathIntoCallable( - PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, - SummaryCtx sc, DataFlowCall call, Configuration config -) { - exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | - pathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - ( - sc = TSummaryCtxSome(p, state, ap) - or - not exists(TSummaryCtxSome(p, state, ap)) and - sc = TSummaryCtxNone() and - // When the call contexts of source and sink needs to match then there's - // never any reason to enter a callable except to find a summary. See also - // the comment in `PathNodeMid::isAtSink`. - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) -} - -/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ -pragma[nomagic] -private predicate paramFlowsThrough( - ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, - AccessPathApprox apa, Configuration config -) { - exists(RetNodeEx ret | - pathNode(_, ret, state, cc, sc, ap, config, _) and - kind = ret.getKind() and - apa = ap.getApprox() and - parameterFlowThroughAllowed(sc.getParamNode(), kind) - ) -} - -pragma[nomagic] -private predicate pathThroughCallable0( - DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(CallContext innercc, SummaryCtx sc | - pathIntoCallable(mid, _, _, cc, innercc, sc, call, config) and - paramFlowsThrough(kind, state, innercc, sc, ap, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` through a callable to the node `out`. - * The context `cc` is restored to its value prior to entering the callable. - */ -pragma[noinline] -private predicate pathThroughCallable( - PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap -) { - exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config | - pathThroughCallable0(call, mid, kind, state, cc, ap, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -private module Subpaths { - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths01( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - exists(Configuration config | - pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and - pathIntoCallable(arg, par, _, _, innercc, sc, _, config) and - paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, - pragma[only_bind_into](apout), _, unbindConf(config)) and - not arg.isHidden() - ) - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `sout`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths02( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and - out.asNode() = kind.getAnOutNode(_) - } - - pragma[nomagic] - private Configuration getPathNodeConf(PathNodeImpl n) { result = n.getConfiguration() } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple. - */ - pragma[nomagic] - private predicate subpaths03( - PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, AccessPath apout - ) { - exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | - subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and - pathNode(ret, retnode, sout, innercc, sc, apout, unbindConf(getPathNodeConf(arg)), _) and - kind = retnode.getKind() - ) - } - - private PathNodeImpl localStepToHidden(PathNodeImpl n) { - n.getASuccessorImpl() = result and - result.isHidden() and - exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | - localFlowBigStep(n1, _, n2, _, _, _, _, _) or - store(n1, _, n2, _, _) or - readSet(n1, _, n2, _) - ) - } - - pragma[nomagic] - private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { - succ = pred.getANonHiddenSuccessor() and - succNode = succ.getNodeEx() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { - exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | - pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and - subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and - hasSuccessor(pragma[only_bind_into](arg), par, p) and - not ret.isHidden() and - pathNode(out0, o, sout, _, _, apout, _, _) - | - out = out0 or out = out0.projectToSink() - ) - } - - /** - * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. - */ - predicate retReach(PathNodeImpl n) { - exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) - or - exists(PathNodeImpl mid | - retReach(mid) and - n.getANonHiddenSuccessor() = mid and - not subpaths(_, mid, _, _) - ) - } -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -private predicate hasFlowPath( - PathNodeImpl flowsource, PathNodeImpl flowsink, Configuration configuration -) { - flowsource.isFlowSource() and - flowsource.getConfiguration() = configuration and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.isFlowSink() -} - -private predicate flowsTo( - PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink, - Configuration configuration -) { - flowsource.isSource() and - flowsource.getConfiguration() = configuration and - flowsource.getNodeEx().asNode() = source and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.getNodeEx().asNode() = sink -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -predicate flowsTo(Node source, Node sink, Configuration configuration) { - flowsTo(_, _, source, sink, configuration) -} - -private predicate finalStats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples -) { - fwd = true and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and - tuples = count(PathNodeImpl pn) - or - fwd = false and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and - tuples = count(PathNode pn) -} - -/** - * INTERNAL: Only for debugging. - * - * Calculates per-stage metrics for data flow. - */ -predicate stageStats( - int n, string stage, int nodes, int fields, int conscand, int states, int tuples, - Configuration config -) { - stage = "1 Fwd" and - n = 10 and - Stage1::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "1 Rev" and - n = 15 and - Stage1::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Fwd" and - n = 20 and - Stage2::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Rev" and - n = 25 and - Stage2::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Fwd" and - n = 30 and - Stage3::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Rev" and - n = 35 and - Stage3::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Fwd" and - n = 40 and - Stage4::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Rev" and - n = 45 and - Stage4::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Fwd" and - n = 50 and - Stage5::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Rev" and - n = 55 and - Stage5::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) - or - stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) -} - -private module FlowExploration { - private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - jumpStep(node1, node2, config) - or - additionalJumpStep(node1, node2, config) - or - additionalJumpStateStep(node1, _, node2, _, config) - or - // flow into callable - viableParamArgEx(_, node2, node1) - or - // flow out of a callable - viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) - | - c1 = node1.getEnclosingCallable() and - c2 = node2.getEnclosingCallable() and - c1 != c2 - ) - } - - private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSource(n) or config.isSource(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSrc(mid, config) and callableStep(mid, c, config) - ) - } - - private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSink(n) or config.isSink(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSink(mid, config) and callableStep(c, mid, config) - ) - } - - private newtype TCallableExt = - TCallable(DataFlowCallable c, Configuration config) { - interestingCallableSrc(c, config) or - interestingCallableSink(c, config) - } or - TCallableSrc() or - TCallableSink() - - private predicate callableExtSrc(TCallableSrc src) { any() } - - private predicate callableExtSink(TCallableSink sink) { any() } - - private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { - exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | - callableStep(c1, c2, config) and - ce1 = TCallable(c1, pragma[only_bind_into](config)) and - ce2 = TCallable(c2, pragma[only_bind_into](config)) - ) - or - exists(Node n, Configuration config | - ce1 = TCallableSrc() and - (config.isSource(n) or config.isSource(n, _)) and - ce2 = TCallable(getNodeEnclosingCallable(n), config) - ) - or - exists(Node n, Configuration config | - ce2 = TCallableSink() and - (config.isSink(n) or config.isSink(n, _)) and - ce1 = TCallable(getNodeEnclosingCallable(n), config) - ) - } - - private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { - callableExtStepFwd(ce2, ce1) - } - - private int distSrcExt(TCallableExt c) = - shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) - - private int distSinkExt(TCallableExt c) = - shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) - - private int distSrc(DataFlowCallable c, Configuration config) { - result = distSrcExt(TCallable(c, config)) - 1 - } - - private int distSink(DataFlowCallable c, Configuration config) { - result = distSinkExt(TCallable(c, config)) - 1 - } - - private newtype TPartialAccessPath = - TPartialNil(DataFlowType t) or - TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first - * element of the list and its length are tracked. If data flows from a source to - * a given node with a given `AccessPath`, this indicates the sequence of - * dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ - private class PartialAccessPath extends TPartialAccessPath { - abstract string toString(); - - TypedContent getHead() { this = TPartialCons(result, _) } - - int len() { - this = TPartialNil(_) and result = 0 - or - this = TPartialCons(_, result) - } - - DataFlowType getType() { - this = TPartialNil(result) - or - exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) - } - } - - private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { - override string toString() { - exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) - } - } - - private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { - override string toString() { - exists(TypedContent tc, int len | this = TPartialCons(tc, len) | - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private newtype TRevPartialAccessPath = - TRevPartialNil() or - TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `Content`s, but only the first - * element of the list and its length are tracked. - */ - private class RevPartialAccessPath extends TRevPartialAccessPath { - abstract string toString(); - - Content getHead() { this = TRevPartialCons(result, _) } - - int len() { - this = TRevPartialNil() and result = 0 - or - this = TRevPartialCons(_, result) - } - } - - private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { - override string toString() { result = "" } - } - - private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { - override string toString() { - exists(Content c, int len | this = TRevPartialCons(c, len) | - if len = 1 - then result = "[" + c.toString() + "]" - else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private predicate relevantState(FlowState state) { - sourceNode(_, state, _) or - sinkNode(_, state, _) or - additionalLocalStateStep(_, state, _, _, _) or - additionalLocalStateStep(_, _, _, state, _) or - additionalJumpStateStep(_, state, _, _, _) or - additionalJumpStateStep(_, _, _, state, _) - } - - private newtype TSummaryCtx1 = - TSummaryCtx1None() or - TSummaryCtx1Param(ParamNodeEx p) - - private newtype TSummaryCtx2 = - TSummaryCtx2None() or - TSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TSummaryCtx3 = - TSummaryCtx3None() or - TSummaryCtx3Some(PartialAccessPath ap) - - private newtype TRevSummaryCtx1 = - TRevSummaryCtx1None() or - TRevSummaryCtx1Some(ReturnPosition pos) - - private newtype TRevSummaryCtx2 = - TRevSummaryCtx2None() or - TRevSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TRevSummaryCtx3 = - TRevSummaryCtx3None() or - TRevSummaryCtx3Some(RevPartialAccessPath ap) - - private newtype TPartialPathNode = - TPartialPathNodeFwd( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = TPartialNil(node.getDataFlowType()) and - exists(config.explorationLimit()) - or - partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap, config) and - distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() - } or - TPartialPathNodeRev( - NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, - RevPartialAccessPath ap, Configuration config - ) { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() and - exists(config.explorationLimit()) - or - revPartialPathStep(_, node, state, sc1, sc2, sc3, ap, config) and - not clearsContentEx(node, ap.getHead()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead()) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() - } - - pragma[nomagic] - private predicate partialPathNodeMk0( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap, config) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - not clearsContentEx(node, ap.getHead().getContent()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead().getContent()) - ) and - if node.asNode() instanceof CastingNode - then compatibleTypes(node.getDataFlowType(), ap.getType()) - else any() - } - - /** - * A `Node` augmented with a call context, an access path, and a configuration. - */ - class PartialPathNode extends TPartialPathNode { - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() - } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - /** Gets the underlying `Node`. */ - final Node getNode() { this.getNodeEx().projectToNode() = result } - - FlowState getState() { none() } - - private NodeEx getNodeEx() { - result = this.(PartialPathNodeFwd).getNodeEx() or - result = this.(PartialPathNodeRev).getNodeEx() - } - - /** Gets the associated configuration. */ - Configuration getConfiguration() { none() } - - /** Gets a successor of this node, if any. */ - PartialPathNode getASuccessor() { none() } - - /** - * Gets the approximate distance to the nearest source measured in number - * of interprocedural steps. - */ - int getSourceDistance() { - result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - /** - * Gets the approximate distance to the nearest sink measured in number - * of interprocedural steps. - */ - int getSinkDistance() { - result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - private string ppAp() { - exists(string s | - s = this.(PartialPathNodeFwd).getAp().toString() or - s = this.(PartialPathNodeRev).getAp().toString() - | - if s = "" then result = "" else result = " " + s - ) - } - - private string ppCtx() { - result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" - } - - /** Holds if this is a source in a forward-flow path. */ - predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } - - /** Holds if this is a sink in a reverse-flow path. */ - predicate isRevSink() { this.(PartialPathNodeRev).isSink() } - } - - /** - * Provides the query predicates needed to include a graph in a path-problem query. - */ - module PartialPathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } - } - - private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { - NodeEx node; - FlowState state; - CallContext cc; - TSummaryCtx1 sc1; - TSummaryCtx2 sc2; - TSummaryCtx3 sc3; - PartialAccessPath ap; - Configuration config; - - PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - TSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TSummaryCtx3 getSummaryCtx3() { result = sc3 } - - PartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeFwd getASuccessor() { - partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp(), - result.getConfiguration()) - } - - predicate isSource() { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap instanceof TPartialNil - } - } - - private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { - NodeEx node; - FlowState state; - TRevSummaryCtx1 sc1; - TRevSummaryCtx2 sc2; - TRevSummaryCtx3 sc3; - RevPartialAccessPath ap; - Configuration config; - - PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } - - RevPartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeRev getASuccessor() { - revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), - this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp(), this.getConfiguration()) - } - - predicate isSink() { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() - } - } - - private predicate partialPathStep( - PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, - TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and - ( - localFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - ) - or - jumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - partialPathStoreStep(mid, _, _, node, ap) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(PartialAccessPath ap0, TypedContent tc | - partialPathReadStep(mid, ap0, tc, node, cc, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsFwd(ap, tc, ap0, config) - ) - or - partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap, config) - or - partialPathOutOfCallable(mid, node, state, cc, ap, config) and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() - or - partialPathThroughCallable(mid, node, state, cc, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - bindingset[result, i] - private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } - - pragma[inline] - private predicate partialPathStoreStep( - PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, - PartialAccessPath ap2 - ) { - exists(NodeEx midNode, DataFlowType contentType | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - store(midNode, tc, node, contentType, mid.getConfiguration()) and - ap2.getHead() = tc and - ap2.len() = unbindInt(ap1.len() + 1) and - compatibleTypes(ap1.getType(), contentType) - ) - } - - pragma[nomagic] - private predicate apConsFwd( - PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeFwd mid | - partialPathStoreStep(mid, ap1, tc, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate partialPathReadStep( - PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc, - Configuration config - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and - ap.getHead() = tc and - pragma[only_bind_into](config) = mid.getConfiguration() and - cc = mid.getCallContext() - ) - } - - private predicate partialPathOutOfCallable0( - PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, - PartialAccessPath ap, Configuration config - ) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - ap = mid.getAp() and - config = mid.getConfiguration() - } - - pragma[nomagic] - private predicate partialPathOutOfCallable1( - PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - partialPathOutOfCallable0(mid, pos, state, innercc, ap, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) - } - - private predicate partialPathOutOfCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(ReturnKindExt kind, DataFlowCall call | - partialPathOutOfCallable1(mid, call, kind, state, cc, ap, config) - | - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[noinline] - private predicate partialPathIntoArg( - PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ArgNode arg, ArgumentPosition apos | - arg = mid.getNodeEx().asNode() and - state = mid.getState() and - cc = mid.getCallContext() and - arg.argumentOf(call, apos) and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate partialPathIntoCallable0( - PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - partialPathIntoArg(mid, pos, state, outercc, call, ap, config) and - callable = resolveCall(call, outercc) - } - - private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, - CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ParameterPosition pos, DataFlowCallable callable | - partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - sc1 = TSummaryCtx1Param(p) and - sc2 = TSummaryCtx2Some(state) and - sc3 = TSummaryCtx3Some(ap) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) - } - - pragma[nomagic] - private predicate paramFlowsThroughInPartialPath( - ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeFwd mid, RetNodeEx ret | - mid.getNodeEx() = ret and - kind = ret.getKind() and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() and - ap = mid.getAp() - ) - } - - pragma[noinline] - private predicate partialPathThroughCallable0( - DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | - partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _, config) and - paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap, config) - ) - } - - private predicate partialPathThroughCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ReturnKindExt kind | - partialPathThroughCallable0(call, mid, kind, state, cc, ap, config) and - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[nomagic] - private predicate revPartialPathStep( - PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, - TRevSummaryCtx3 sc3, RevPartialAccessPath ap, Configuration config - ) { - localFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - jumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - revPartialPathReadStep(mid, _, _, node, ap) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(RevPartialAccessPath ap0, Content c | - revPartialPathStoreStep(mid, ap0, c, node, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsRev(ap, c, ap0, config) - ) - or - exists(ParamNodeEx p | - mid.getNodeEx() = p and - viableParamArgEx(_, p, node) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - or - exists(ReturnPosition pos | - revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap, config) and - pos = getReturnPosition(node.asNode()) - ) - or - revPartialPathThroughCallable(mid, node, state, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - pragma[inline] - private predicate revPartialPathReadStep( - PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, - RevPartialAccessPath ap2 - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - read(node, c, midNode, mid.getConfiguration()) and - ap2.getHead() = c and - ap2.len() = unbindInt(ap1.len() + 1) - ) - } - - pragma[nomagic] - private predicate apConsRev( - RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeRev mid | - revPartialPathReadStep(mid, ap1, c, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathStoreStep( - PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config - ) { - exists(NodeEx midNode, TypedContent tc | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - store(node, tc, midNode, _, config) and - ap.getHead() = c and - config = mid.getConfiguration() and - tc.getContent() = c - ) - } - - pragma[nomagic] - private predicate revPartialPathIntoReturn( - PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, - TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap, - Configuration config - ) { - exists(NodeEx out | - mid.getNodeEx() = out and - mid.getState() = state and - viableReturnPosOutEx(call, pos, out) and - sc1 = TRevSummaryCtx1Some(pos) and - sc2 = TRevSummaryCtx2Some(state) and - sc3 = TRevSummaryCtx3Some(ap) and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathFlowsThrough( - ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, - TRevSummaryCtx3Some sc3, RevPartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | - mid.getNodeEx() = p and - mid.getState() = state and - p.getPosition() = ppos and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable0( - DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, - RevPartialAccessPath ap, Configuration config - ) { - exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | - revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _, config) and - revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap, config) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ArgumentPosition pos | - revPartialPathThroughCallable0(call, mid, pos, state, ap, config) and - node.asNode().(ArgNode).argumentOf(call, pos) - ) - } -} - -import FlowExploration - -private predicate partialFlow( - PartialPathNode source, PartialPathNode node, Configuration configuration -) { - source.getConfiguration() = configuration and - source.isFwdSource() and - node = source.getASuccessor+() -} - -private predicate revPartialFlow( - PartialPathNode node, PartialPathNode sink, Configuration configuration -) { - sink.getConfiguration() = configuration and - sink.isRevSink() and - node.getASuccessor+() = sink -} +predicate flowsTo = hasFlow/3; diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll index 1b969756b09..e6bdc74cceb 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll @@ -1,16 +1,16 @@ /** - * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from - * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed - * through the `Configuration` class. This file exists in several identical - * copies, allowing queries to use multiple `Configuration` classes that depend - * on each other without introducing mutual recursion among those configurations. + * DEPRECATED: Use `Make` and `MakeWithState` instead. + * + * Provides a `Configuration` class backwards-compatible interface to the data + * flow library. */ private import DataFlowImplCommon private import DataFlowImplSpecific::Private import DataFlowImplSpecific::Public +private import DataFlowImpl import DataFlowImplCommonPublic +import FlowStateString /** * A configuration of interprocedural data flow analysis. This defines @@ -144,6 +144,10 @@ abstract class Configuration extends string { * - `FeatureEqualSourceSinkCallContext`: * Implies both of the above and additionally ensures that the entire flow * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. */ FlowFeature getAFeature() { none() } @@ -156,7 +160,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from `source` to `sink` for this configuration. */ - predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } + predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) } /** * Holds if data may flow from `source` to `sink` for this configuration. @@ -169,9 +173,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from some source to `sink` for this configuration. */ - predicate hasFlowTo(Node sink) { - sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode() - } + predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) } /** * Holds if data may flow from some source to `sink` for this configuration. @@ -179,10 +181,12 @@ abstract class Configuration extends string { predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } /** + * DEPRECATED: Use `FlowExploration` instead. + * * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` * measured in approximate number of interprocedural steps. */ - int explorationLimit() { none() } + deprecated int explorationLimit() { none() } /** * Holds if hidden nodes should be included in the data flow graph. @@ -191,49 +195,6 @@ abstract class Configuration extends string { * is not visualized (for example in a `path-problem` query). */ predicate includeHiddenNodes() { none() } - - /** - * Holds if there is a partial data flow path from `source` to `node`. The - * approximate distance between `node` and the closest source is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards sink definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sources is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - */ - final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { - partialFlow(source, node, this) and - dist = node.getSourceDistance() - } - - /** - * Holds if there is a partial data flow path from `node` to `sink`. The - * approximate distance between `node` and the closest sink is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards source definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sinks is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - * - * Note that reverse flow has slightly lower precision than the corresponding - * forward flow, as reverse flow disregards type pruning among other features. - */ - final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { - revPartialFlow(node, sink, this) and - dist = node.getSinkDistance() - } } /** @@ -263,90 +224,6 @@ abstract private class ConfigurationRecursionPrevention extends Configuration { } } -private newtype TNodeEx = - TNodeNormal(Node n) or - TNodeImplicitRead(Node n, boolean hasRead) { - any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true] - } - -private class NodeEx extends TNodeEx { - string toString() { - result = this.asNode().toString() - or - exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") - } - - Node asNode() { this = TNodeNormal(result) } - - predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } - - Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } - - pragma[nomagic] - private DataFlowCallable getEnclosingCallable0() { - nodeEnclosingCallable(this.projectToNode(), result) - } - - pragma[inline] - DataFlowCallable getEnclosingCallable() { - pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) - } - - pragma[nomagic] - private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } - - pragma[inline] - DataFlowType getDataFlowType() { - pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) - } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -private class ArgNodeEx extends NodeEx { - ArgNodeEx() { this.asNode() instanceof ArgNode } -} - -private class ParamNodeEx extends NodeEx { - ParamNodeEx() { this.asNode() instanceof ParamNode } - - predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { - this.asNode().(ParamNode).isParameterOf(c, pos) - } - - ParameterPosition getPosition() { this.isParameterOf(_, result) } -} - -private class RetNodeEx extends NodeEx { - RetNodeEx() { this.asNode() instanceof ReturnNodeExt } - - ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } - - ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } -} - -private predicate inBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierIn(n) - | - config.isSource(n) or config.isSource(n, _) - ) -} - -private predicate outBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierOut(n) - | - config.isSink(n) or config.isSink(n, _) - ) -} - /** A bridge class to access the deprecated `isBarrierGuard`. */ private class BarrierGuardGuardedNodeBridge extends Unit { abstract predicate guardedNode(Node n, Configuration config); @@ -370,3019 +247,94 @@ private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { } } -pragma[nomagic] -private predicate fullBarrier(NodeEx node, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n) +private FlowState relevantState(Configuration config) { + config.isSource(_, result) or + config.isSink(_, result) or + config.isBarrier(_, result) or + config.isAdditionalFlowStep(_, result, _, _) or + config.isAdditionalFlowStep(_, _, _, result) +} + +private newtype TConfigState = + TMkConfigState(Configuration config, FlowState state) { + state = relevantState(config) or state instanceof FlowStateEmpty + } + +private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) } + +private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) } + +private predicate singleConfiguration() { 1 = strictcount(Configuration c) } + +private module Config implements FullStateConfigSig { + class FlowState = TConfigState; + + predicate isSource(Node source, FlowState state) { + getConfig(state).isSource(source, getState(state)) or - config.isBarrierIn(n) and - not config.isSource(n) and - not config.isSource(n, _) + getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty + } + + predicate isSink(Node sink, FlowState state) { + getConfig(state).isSink(sink, getState(state)) or - config.isBarrierOut(n) and - not config.isSink(n) and - not config.isSink(n, _) + getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty + } + + predicate isBarrier(Node node) { none() } + + predicate isBarrier(Node node, FlowState state) { + getConfig(state).isBarrier(node, getState(state)) or + getConfig(state).isBarrier(node) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state)) + } + + predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) } + + predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) } + + predicate isAdditionalFlowStep(Node node1, Node node2) { + singleConfiguration() and + any(Configuration config).isAdditionalFlowStep(node1, node2) + } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and + getConfig(state2) = getConfig(state1) or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config) - ) -} - -pragma[nomagic] -private predicate stateBarrier(NodeEx node, FlowState state, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n, state) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config) - ) -} - -pragma[nomagic] -private predicate sourceNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSource(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSource(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -pragma[nomagic] -private predicate sinkNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSink(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSink(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -/** Provides the relevant barriers for a step from `node1` to `node2`. */ -pragma[inline] -private predicate stepFilter(NodeEx node1, NodeEx node2, Configuration config) { - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) -} - -/** - * Holds if data can flow in one local step from `node1` to `node2`. - */ -private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.asNode() = n and - node2.isImplicitReadNode(n, false) and - not fullBarrier(node1, config) - ) -} - -/** - * Holds if the additional step from `node1` to `node2` does not jump between callables. - */ -private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.isImplicitReadNode(n, true) and - node2.asNode() = n and - not fullBarrier(node2, config) - ) -} - -private predicate additionalLocalStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) - ) -} - -/** - * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. - */ -private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if the additional step from `node1` to `node2` jumps between callables. - */ -private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate additionalJumpStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -pragma[nomagic] -private predicate readSet(NodeEx node1, ContentSet c, NodeEx node2, Configuration config) { - readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and - stepFilter(node1, node2, config) - or - exists(Node n | - node2.isImplicitReadNode(n, true) and - node1.isImplicitReadNode(n, _) and - config.allowImplicitRead(n, c) - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(ContentSet cs | - readSet(node1, cs, node2, config) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate clearsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - clearsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate expectsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - expectsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -pragma[nomagic] -private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } - -pragma[nomagic] -private predicate hasReadStep(Content c, Configuration config) { read(_, c, _, config) } - -pragma[nomagic] -private predicate store( - NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config -) { - store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), - contentType) and - hasReadStep(tc.getContent(), config) and - stepFilter(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { - viableReturnPosOut(call, pos, out.asNode()) -} - -pragma[nomagic] -private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { - viableParamArg(call, p.asNode(), arg.asNode()) -} - -/** - * Holds if field flow should be used for the given configuration. - */ -private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } - -private predicate hasSourceCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSourceCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate hasSinkCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSinkCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if flow from `p` to a return node of kind `kind` is allowed. - * - * We don't expect a parameter to return stored in itself, unless - * explicitly allowed - */ -bindingset[p, kind] -private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { - exists(ParameterPosition pos | p.isParameterOf(_, pos) | - not kind.(ParamUpdateReturnKind).getPosition() = pos - or - allowParameterReturnInSelfCached(p.asNode()) - ) -} - -private module Stage1 implements StageSig { - class Ap extends int { - // workaround for bad functionality-induced joins (happens when using `Unit`) - pragma[nomagic] - Ap() { this in [0 .. 1] and this < 1 } - } - - private class Cc = boolean; - - /* Begin: Stage 1 logic. */ - /** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `cc` records whether the node is reached through an - * argument in a call. - */ - private predicate fwdFlow(NodeEx node, Cc cc, Configuration config) { - sourceNode(node, _, config) and - if hasSourceCallCtx(config) then cc = true else cc = false - or - exists(NodeEx mid | fwdFlow(mid, cc, config) | - localFlowStep(mid, node, config) or - additionalLocalFlowStep(mid, node, config) or - additionalLocalStateStep(mid, _, node, _, config) - ) - or - exists(NodeEx mid | fwdFlow(mid, _, config) and cc = false | - jumpStep(mid, node, config) or - additionalJumpStep(mid, node, config) or - additionalJumpStateStep(mid, _, node, _, config) - ) - or - // store - exists(NodeEx mid | - useFieldFlow(config) and - fwdFlow(mid, cc, config) and - store(mid, _, node, _, config) - ) - or - // read - exists(ContentSet c | - fwdFlowReadSet(c, node, cc, config) and - fwdFlowConsCandSet(c, _, config) - ) - or - // flow into a callable - fwdFlowIn(_, _, _, node, config) and - cc = true - or - // flow out of a callable - fwdFlowOut(_, node, false, config) and - cc = false - or - // flow through a callable - exists(DataFlowCall call | - fwdFlowOutFromArg(call, node, config) and - fwdFlowIsEntered(call, cc, config) - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowIn( - DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p, Configuration config - ) { - // call context cannot help reduce virtual dispatch - fwdFlow(arg, cc, config) and - viableParamArgEx(call, p, arg) and - not fullBarrier(p, config) and - ( - cc = false - or - cc = true and - not reducedViableImplInCallContext(call, _, _) - ) - or - // call context may help reduce virtual dispatch - exists(DataFlowCallable target | - fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target, config) and - target = viableImplInSomeFwdFlowCallContextExt(call, config) and - cc = true - ) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - fwdFlowIn(call, _, cc, _, config) - } - - pragma[nomagic] - private predicate fwdFlowInReducedViableImplInSomeCallContext( - DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target, Configuration config - ) { - fwdFlow(arg, true, config) and - viableParamArgEx(call, p, arg) and - reducedViableImplInCallContext(call, _, _) and - target = p.getEnclosingCallable() and - not fullBarrier(p, config) - } - - /** - * Gets a viable dispatch target of `call` in the context `ctx`. This is - * restricted to those `call`s for which a context might make a difference, - * and to `ctx`s that are reachable in `fwdFlow`. - */ - pragma[nomagic] - private DataFlowCallable viableImplInSomeFwdFlowCallContextExt( - DataFlowCall call, Configuration config - ) { - exists(DataFlowCall ctx | - fwdFlowIsEntered(ctx, _, config) and - result = viableImplInCallContextExt(call, ctx) - ) - } - - private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) } - - pragma[nomagic] - private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc, Configuration config) { - exists(NodeEx mid | - fwdFlow(mid, cc, config) and - readSet(mid, c, node, config) - ) - } - - /** - * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - fwdFlow(mid, _, config) and - store(mid, tc, node, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `cs` may be interpreted in a read as the target of some store - * into `c`, in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCandSet(ContentSet cs, Content c, Configuration config) { - fwdFlowConsCand(c, config) and - c = cs.getAReadContent() - } - - pragma[nomagic] - private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { - exists(RetNodeEx ret | - fwdFlow(ret, cc, config) and - ret.getReturnPosition() = pos - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) { - exists(ReturnPosition pos | - fwdFlowReturnPosition(pos, cc, config) and - viableReturnPosOutEx(call, pos, out) and - not fullBarrier(out, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) { - fwdFlowOut(call, out, true, config) - } - - private predicate stateStepFwd(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1 | - additionalLocalStateStep(node1, state1, _, state2, config) or - additionalJumpStateStep(node1, state1, _, state2, config) - | - fwdFlow(node1, config) - ) - } - - private predicate fwdFlowState(FlowState state, Configuration config) { - sourceNode(_, state, config) - or - exists(FlowState state0 | - fwdFlowState(state0, config) and - stateStepFwd(state0, state, config) - ) - } - - /** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink. - */ - pragma[nomagic] - private predicate revFlow(NodeEx node, boolean toReturn, Configuration config) { - revFlow0(node, toReturn, config) and - fwdFlow(node, config) - } - - pragma[nomagic] - private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) { - exists(FlowState state | - fwdFlow(node, pragma[only_bind_into](config)) and - sinkNode(node, state, config) and - fwdFlowState(state, pragma[only_bind_into](config)) and - if hasSinkCallCtx(config) then toReturn = true else toReturn = false - ) - or - exists(NodeEx mid | revFlow(mid, toReturn, config) | - localFlowStep(node, mid, config) or - additionalLocalFlowStep(node, mid, config) or - additionalLocalStateStep(node, _, mid, _, config) - ) - or - exists(NodeEx mid | revFlow(mid, _, config) and toReturn = false | - jumpStep(node, mid, config) or - additionalJumpStep(node, mid, config) or - additionalJumpStateStep(node, _, mid, _, config) - ) - or - // store - exists(Content c | - revFlowStore(c, node, toReturn, config) and - revFlowConsCand(c, config) - ) - or - // read - exists(NodeEx mid, ContentSet c | - readSet(node, c, mid, config) and - fwdFlowConsCandSet(c, _, pragma[only_bind_into](config)) and - revFlow(mid, toReturn, pragma[only_bind_into](config)) - ) - or - // flow into a callable - revFlowIn(_, node, false, config) and - toReturn = false - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(pos, config) and - node.(RetNodeEx).getReturnPosition() = pos and - toReturn = true - ) - or - // flow through a callable - exists(DataFlowCall call | - revFlowInToReturn(call, node, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - /** - * Holds if `c` is the target of a read in the flow covered by `revFlow`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, ContentSet cs | - fwdFlow(node, pragma[only_bind_into](config)) and - readSet(node, cs, mid, config) and - fwdFlowConsCandSet(cs, c, pragma[only_bind_into](config)) and - revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) { - exists(NodeEx mid, TypedContent tc | - revFlow(mid, toReturn, pragma[only_bind_into](config)) and - fwdFlowConsCand(c, pragma[only_bind_into](config)) and - store(node, tc, mid, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `c` is the target of both a read and a store in the flow covered - * by `revFlow`. - */ - pragma[nomagic] - additional predicate revFlowIsReadAndStored(Content c, Configuration conf) { - revFlowConsCand(c, conf) and - revFlowStore(c, _, _, conf) - } - - pragma[nomagic] - additional predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config - ) { - fwdFlowReturnPosition(pos, _, config) and - viableReturnPosOutEx(call, pos, out) - } - - pragma[nomagic] - private predicate revFlowOut(ReturnPosition pos, Configuration config) { - exists(NodeEx out | - revFlow(out, _, config) and - viableReturnPosOutNodeCandFwd1(_, pos, out, config) - ) - } - - pragma[nomagic] - additional predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config - ) { - fwdFlowIn(call, arg, _, p, config) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate revFlowIn( - DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config - ) { - exists(ParamNodeEx p | - revFlow(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) { - revFlowIn(call, arg, true, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(NodeEx out | - revFlow(out, toReturn, config) and - fwdFlowOutFromArg(call, out, config) - ) - } - - private predicate stateStepRev(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - additionalLocalStateStep(node1, state1, node2, state2, config) or - additionalJumpStateStep(node1, state1, node2, state2, config) - | - revFlow(node1, _, pragma[only_bind_into](config)) and - revFlow(node2, _, pragma[only_bind_into](config)) and - fwdFlowState(state1, pragma[only_bind_into](config)) and - fwdFlowState(state2, pragma[only_bind_into](config)) - ) - } - - additional predicate revFlowState(FlowState state, Configuration config) { - exists(NodeEx node | - sinkNode(node, state, config) and - revFlow(node, _, pragma[only_bind_into](config)) and - fwdFlowState(state, pragma[only_bind_into](config)) - ) - or - exists(FlowState state0 | - revFlowState(state0, config) and - stateStepRev(state, state0, config) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Content c | - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - revFlow(node2, pragma[only_bind_into](config)) and - store(node1, tc, node2, contentType, config) and - c = tc.getContent() and - exists(ap1) - ) - } - - pragma[nomagic] - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) { - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - read(n1, c, n2, pragma[only_bind_into](config)) and - revFlow(n2, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, config) and - exists(ap) - } - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, _, pragma[only_bind_into](config)) and - exists(state) and - exists(ap) - } - - private predicate throughFlowNodeCand(NodeEx node, Configuration config) { - revFlow(node, true, config) and - fwdFlow(node, true, config) and - not inBarrier(node, config) and - not outBarrier(node, config) - } - - /** Holds if flow may return from `callable`. */ - pragma[nomagic] - private predicate returnFlowCallableNodeCand( - DataFlowCallable callable, ReturnKindExt kind, Configuration config - ) { - exists(RetNodeEx ret | - throughFlowNodeCand(ret, config) and - callable = ret.getEnclosingCallable() and - kind = ret.getKind() - ) - } - - /** - * Holds if flow may enter through `p` and reach a return node making `p` a - * candidate for the origin of a summary. - */ - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(DataFlowCallable c, ReturnKindExt kind | - throughFlowNodeCand(p, config) and - returnFlowCallableNodeCand(c, kind, config) and - p.getEnclosingCallable() = c and - exists(ap) and - parameterFlowThroughAllowed(p, kind) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - throughFlowNodeCand(ret, config) and - kind = ret.getKind() and - exists(argAp) and - exists(ap) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, boolean toReturn | - revFlow(arg, toReturn, config) and - revFlowInToReturn(call, arg, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, config)) and - fields = count(Content f0 | fwdFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | fwdFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, config)) and - fields = count(Content f0 | revFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | revFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | revFlow(n, b, config)) - } - /* End: Stage 1 logic. */ -} - -pragma[noinline] -private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - localFlowStep(node1, node2, config) -} - -pragma[noinline] -private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - additionalLocalFlowStep(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCand1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config -) { - Stage1::revFlow(out, config) and - Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, Configuration config -) { - exists(ReturnPosition pos | - viableReturnPosOutNodeCand1(call, pos, out, config) and - pos = ret.getReturnPosition() and - kind = pos.getKind() and - Stage1::revFlow(ret, config) and - not outBarrier(ret, config) and - not inBarrier(out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCand1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config -) { - Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and - Stage1::revFlow(arg, config) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config -) { - viableParamArgNodeCand1(call, p, arg, config) and - Stage1::revFlow(p, config) and - not outBarrier(arg, config) and - not inBarrier(p, config) -} - -/** - * Gets the amount of forward branching on the origin of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int branch(NodeEx n1, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n1, _, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) - ) -} - -/** - * Gets the amount of backward branching on the target of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int join(NodeEx n2, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n, _, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) - ) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. The - * `allowsFieldFlow` flag indicates whether the branching is within the limit - * specified by the configuration. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, ret, kind, out, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(ret, pragma[only_bind_into](config)) and - j = join(out, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. The `allowsFieldFlow` flag indicates whether - * the branching is within the limit specified by the configuration. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config -) { - flowIntoCallNodeCand1(call, arg, p, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(arg, pragma[only_bind_into](config)) and - j = join(p, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -private signature module StageSig { - class Ap; - - predicate revFlow(NodeEx node, Configuration config); - - predicate revFlowAp(NodeEx node, Ap ap, Configuration config); - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config); - - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config); - - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config); - - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ); - - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ); - - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config); -} - -private module MkStage { - class ApApprox = PrevStage::Ap; - - signature module StageParam { - class Ap; - - class ApNil extends Ap; - - bindingset[result, ap] - ApApprox getApprox(Ap ap); - - ApNil getApNil(NodeEx node); - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail); - - /** - * An approximation of `Content` that corresponds to the precision level of - * `Ap`, such that the mappings from both `Ap` and `Content` to this type - * are functional. - */ - class ApHeadContent; - - ApHeadContent getHeadContent(Ap ap); - - ApHeadContent projectToHeadContent(Content c); - - class ApOption; - - ApOption apNone(); - - ApOption apSome(Ap ap); - - class Cc; - - class CcCall extends Cc; - - // TODO: member predicate on CcCall - predicate matchesCall(CcCall cc, DataFlowCall call); - - class CcNoCall extends Cc; - - Cc ccNone(); - - CcCall ccSomeCall(); - - class LocalCc; - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc); - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ); - - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config - ); - - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config - ); - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config); - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType); - } - - module Stage implements StageSig { - import Param - - /* Begin: Stage logic. */ - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - private predicate revFlowApAlias(NodeEx node, ApApprox apa, Configuration config) { - PrevStage::revFlowAp(node, apa, config) - } - - pragma[nomagic] - private predicate flowIntoCallApa( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa, - Configuration config - ) { - flowIntoCall(call, arg, p, allowsFieldFlow, config) and - PrevStage::revFlowAp(p, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(arg, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowOutOfCallApa( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - ApApprox apa, Configuration config - ) { - flowOutOfCall(call, ret, kind, out, allowsFieldFlow, config) and - PrevStage::revFlowAp(out, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(ret, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowThroughOutOfCall( - DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, - ApApprox argApa, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa, pragma[only_bind_into](config)) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) and - matchesCall(ccc, call) - ) - } - - /** - * Holds if `node` is reachable with access path `ap` from a source in the - * configuration `config`. - * - * The call context `cc` records whether the node is reached through an - * argument in a call, and if so, `summaryCtx` and `argAp` record the - * corresponding parameter position and access path of that argument, respectively. - */ - pragma[nomagic] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa, config) and - PrevStage::revFlow(node, state, apa, config) and - filter(node, state, ap, config) - } - - pragma[inline] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, cc, summaryCtx, argAp, ap, _, config) - } - - pragma[nomagic] - private predicate fwdFlow0( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - sourceNode(node, state, config) and - (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and - argAp = apNone() and - summaryCtx = TParamNodeNone() and - ap = getApNil(node) and - apa = getApprox(ap) - or - exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | - fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0, config) and - localCc = getLocalCc(mid, cc) - | - localStep(mid, state0, node, state, true, _, config, localCc) and - ap = ap0 and - apa = apa0 - or - localStep(mid, state0, node, state, false, ap, config, localCc) and - ap0 instanceof ApNil and - apa = getApprox(ap) - ) - or - exists(NodeEx mid | - fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa, pragma[only_bind_into](config)) and - jumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(mid, state, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(mid, state0, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStateStep(mid, state0, node, state, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - // store - exists(TypedContent tc, Ap ap0 | - fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp, config) and - ap = apCons(tc, ap0) and - apa = getApprox(ap) - ) - or - // read - exists(Ap ap0, Content c | - fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp, config) and - fwdFlowConsCand(ap0, c, ap, config) and - apa = getApprox(ap) - ) - or - // flow into a callable - fwdFlowIn(_, node, state, _, cc, _, _, ap, apa, config) and - if PrevStage::parameterMayFlowThrough(node, apa, config) - then ( - summaryCtx = TParamNodeSome(node.asNode()) and - argAp = apSome(ap) - ) else ( - summaryCtx = TParamNodeNone() and argAp = apNone() - ) - or - // flow out of a callable - exists( - DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, - DataFlowCallable inner - | - fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa, config) and - flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa, config) and - inner = ret.getEnclosingCallable() and - cc = getCallContextReturn(inner, call, innercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - or - // flow through a callable - exists( - DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, ApApprox innerArgApa - | - fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowStore( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(DataFlowType contentType, ApApprox apa1 | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1, config) and - PrevStage::storeStepCand(node1, apa1, tc, node2, contentType, config) and - typecheckStore(ap1, contentType) - ) - } - - /** - * Holds if forward flow with access path `tail` reaches a store of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(TypedContent tc | - fwdFlowStore(_, tail, tc, _, _, _, _, _, config) and - tc.getContent() = c and - cons = apCons(tc, tail) - ) - } - - pragma[nomagic] - private predicate readStepCand( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - PrevStage::readStepCand(node1, c, node2, config) and - apc = projectToHeadContent(c) - } - - bindingset[node1, apc] - pragma[inline_late] - private predicate readStepCand0( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - readStepCand(node1, apc, c, node2, config) - } - - pragma[nomagic] - private predicate fwdFlowRead( - Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(ApHeadContent apc | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap, config) and - apc = getHeadContent(ap) and - readStepCand0(node1, apc, c, node2, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowIn( - DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, - ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa, Configuration config - ) { - exists(ArgNodeEx arg, boolean allowsFieldFlow | - fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa, config) and - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowRetFromArg( - RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, ApApprox argApa, - Ap ap, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - fwdFlow(pragma[only_bind_into](ret), state, ccc, - TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), - pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa), - pragma[only_bind_into](config)) and - kind = ret.getKind() and - parameterFlowThroughAllowed(summaryCtx, kind) and - argApa = getApprox(argAp) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) - ) - } - - pragma[inline] - private predicate fwdFlowThrough0( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, - Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa, config) and - fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp, config) - } - - pragma[nomagic] - private predicate fwdFlowThrough( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa, - config) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` - * and data might flow through the target callable and back out at `call`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered( - DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, - ParamNodeEx p, Ap ap, Configuration config - ) { - exists(ApApprox apa | - fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(p, apa, config) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate storeStepFwd( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config - ) { - fwdFlowStore(node1, ap1, tc, node2, _, _, _, _, config) and - ap2 = apCons(tc, ap1) and - fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _, config) - } - - private predicate readStepFwd( - NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config - ) { - fwdFlowRead(ap1, c, n1, n2, _, _, _, _, config) and - fwdFlowConsCand(ap1, c, ap2, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough0( - DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, - ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, - innerArgApa, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough( - RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, - Ap ap, Configuration config - ) { - exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | - returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa, config) and - pos = ret.getReturnPosition() and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowThroughIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap, - Configuration config - ) { - exists(ApApprox argApa | - flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), - allowsFieldFlow, argApa, pragma[only_bind_into](config)) and - fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa, - pragma[only_bind_into](config)) and - returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap, - pragma[only_bind_into](config)) and - if allowsFieldFlow = false then argAp instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowIntoCallAp( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap, - Configuration config - ) { - exists(ApApprox apa | - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - fwdFlow(arg, _, _, _, _, ap, apa, config) - ) - } - - pragma[nomagic] - private predicate flowOutOfCallAp( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, - Ap ap, Configuration config - ) { - exists(ApApprox apa | - flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa, config) and - fwdFlow(ret, _, _, _, _, ap, apa, config) and - pos = ret.getReturnPosition() - ) - } - - /** - * Holds if `node` with access path `ap` is part of a path from a source to a - * sink in the configuration `config`. - * - * The parameter `returnCtx` records whether (and how) the node must be returned - * from the enclosing callable in order to reach a sink, and if so, `returnAp` - * records the access path of the returned value. - */ - pragma[nomagic] - additional predicate revFlow( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - revFlow0(node, state, returnCtx, returnAp, ap, config) and - fwdFlow(node, state, _, _, _, ap, config) - } - - pragma[nomagic] - private predicate revFlow0( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, _, _, _, ap, config) and - sinkNode(node, state, config) and - ( - if hasSinkCallCtx(config) - then returnCtx = TReturnCtxNoFlowThrough() - else returnCtx = TReturnCtxNone() - ) and - returnAp = apNone() and - ap instanceof ApNil - or - exists(NodeEx mid, FlowState state0 | - localStep(node, state, mid, state0, true, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, ap, config) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap, pragma[only_bind_into](config)) and - localStep(node, pragma[only_bind_into](state), mid, state0, false, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, nil, pragma[only_bind_into](config)) and - ap instanceof ApNil - ) - or - exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, state, _, _, ap, config) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStep(node, mid, config) and - revFlow(pragma[only_bind_into](mid), state, _, _, nil, pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStateStep(node, state, mid, state0, config) and - revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil, - pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - // store - exists(Ap ap0, Content c | - revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp, config) and - revFlowConsCand(ap0, c, ap, config) - ) - or - // read - exists(NodeEx mid, Ap ap0 | - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - readStepFwd(node, ap, _, mid, ap0, config) - ) - or - // flow into a callable - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, state, TReturnCtxNone(), returnAp, ap, config) and - flowIntoCallAp(_, node, p, allowsFieldFlow, ap, config) and - (if allowsFieldFlow = false then ap instanceof ApNil else any()) and - returnCtx = TReturnCtxNone() - ) - or - // flow through a callable - exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, node, p, _, ap, innerReturnAp, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(_, node, pos, state, _, _, ap, config) and - if returnFlowsThrough(node, pos, state, _, _, _, ap, config) - then ( - returnCtx = TReturnCtxMaybeFlowThrough(pos) and - returnAp = apSome(ap) - ) else ( - returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() - ) - ) - } - - pragma[nomagic] - private predicate revFlowStore( - Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, - ReturnCtx returnCtx, ApOption returnAp, Configuration config - ) { - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - storeStepFwd(node, ap, tc, mid, ap0, config) and - tc.getContent() = c - } - - /** - * Holds if reverse flow with access path `tail` reaches a read of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(NodeEx mid, Ap tail0 | - revFlow(mid, _, _, _, tail, config) and - tail = pragma[only_bind_into](tail0) and - readStepFwd(_, cons, c, mid, tail0, config) - ) - } - - pragma[nomagic] - private predicate revFlowOut( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, - ApOption returnAp, Ap ap, Configuration config - ) { - exists(NodeEx out, boolean allowsFieldFlow | - revFlow(out, state, returnCtx, returnAp, ap, config) and - flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate revFlowParamToReturn( - ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap, Configuration config - ) { - revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), - pragma[only_bind_into](ap), pragma[only_bind_into](config)) and - parameterFlowThroughAllowed(p, pos.getKind()) and - PrevStage::parameterMayFlowThrough(p, getApprox(ap), config) - } - - pragma[nomagic] - private predicate revFlowThrough( - DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, - ApOption returnAp, Ap ap, Ap innerReturnAp, Configuration config - ) { - revFlowParamToReturn(p, state, pos, innerReturnAp, ap, config) and - revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned( - DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap, - Configuration config - ) { - exists(RetNodeEx ret, FlowState state, CcCall ccc | - revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap, config) and - returnFlowsThrough(ret, pos, state, ccc, _, _, ap, config) and - matchesCall(ccc, call) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Ap ap2, Content c | - PrevStage::storeStepCand(node1, _, tc, node2, contentType, config) and - revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _, config) and - revFlowConsCand(ap2, c, ap1, config) - ) - } - - predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(Ap ap1, Ap ap2 | - revFlow(node2, _, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and - readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _, - pragma[only_bind_into](config)) - ) - } - - additional predicate revFlow(NodeEx node, FlowState state, Configuration config) { - revFlow(node, state, _, _, _, config) - } - - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, _, _, ap, config) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, _, _, _, ap, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, Configuration config) { - revFlow(node, _, _, _, _, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, ap, config) - } - - private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepFwd(_, ap, tc, _, _, config) - } - - private predicate revConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepCand(_, ap, tc, _, _, config) - } - - private predicate validAp(Ap ap, Configuration config) { - revFlow(_, _, _, _, ap, config) and ap instanceof ApNil - or - exists(TypedContent head, Ap tail | - consCand(head, tail, config) and - ap = apCons(head, tail) - ) - } - - additional predicate consCand(TypedContent tc, Ap ap, Configuration config) { - revConsCand(tc, ap, config) and - validAp(ap, config) - } - - pragma[nomagic] - private predicate parameterFlowsThroughRev( - ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp, Configuration config - ) { - revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap, config) and - parameterFlowThroughAllowed(p, pos.getKind()) - } - - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(ReturnPosition pos | - returnFlowsThrough(_, pos, _, _, p, ap, _, config) and - parameterFlowsThroughRev(p, ap, pos, _, config) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - exists(ParamNodeEx p, ReturnPosition pos | - returnFlowsThrough(ret, pos, _, _, p, argAp, ap, config) and - parameterFlowsThroughRev(p, argAp, pos, ap, config) and - kind = pos.getKind() - ) - } - - pragma[nomagic] - private predicate revFlowThroughArg( - DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, - Ap ap, Configuration config - ) { - exists(ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp, config) - ) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | - revFlow(arg, state, returnCtx, returnAp, ap, config) and - revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, config)) and - fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and - states = count(FlowState state | fwdFlow(_, state, _, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | - fwdFlow(n, state, cc, summaryCtx, argAp, ap, config) - ) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, _, _, _, config)) and - fields = count(TypedContent f0 | consCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and - states = count(FlowState state | revFlow(_, state, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | - revFlow(n, state, returnCtx, retAp, ap, config) - ) - } - /* End: Stage logic. */ - } -} - -private module BooleanCallContext { - class Cc extends boolean { - Cc() { this in [true, false] } - } - - class CcCall extends Cc { - CcCall() { this = true } - } - - /** Holds if the call context may be `call`. */ - predicate matchesCall(CcCall cc, DataFlowCall call) { any() } - - class CcNoCall extends Cc { - CcNoCall() { this = false } - } - - Cc ccNone() { result = false } - - CcCall ccSomeCall() { result = true } - - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } -} - -private module Level1CallContext { - class Cc = CallContext; - - class CcCall = CallContextCall; - - pragma[inline] - predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } - - class CcNoCall = CallContextNoCall; - - Cc ccNone() { result instanceof CallContextAny } - - CcCall ccSomeCall() { result instanceof CallContextSomeCall } - - module NoLocalCallContext { - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSiteDispatch(call, c) - then result = TSpecificCall(call) - else result = TSomeCall() - } - } - - module LocalCallContext { - class LocalCc = LocalCallContext; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { - result = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - node.getEnclosingCallable()) - } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() - } - } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { - checkCallContextReturn(innercc, c, call) and - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() - } -} - -private module Stage2Param implements MkStage::StageParam { - private module PrevStage = Stage1; - - class Ap extends boolean { - Ap() { this in [true, false] } - } - - class ApNil extends Ap { - ApNil() { this = false } - } - - bindingset[result, ap] - PrevStage::Ap getApprox(Ap ap) { any() } - - ApNil getApNil(NodeEx node) { Stage1::revFlow(node, _) and exists(result) } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } - - class ApHeadContent = Unit; - - pragma[inline] - ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } - - ApHeadContent projectToHeadContent(Content c) { any() } - - class ApOption = BooleanOption; - - ApOption apNone() { result = TBooleanNone() } - - ApOption apSome(Ap ap) { result = TBooleanSome(ap) } - - import Level1CallContext - import NoLocalCallContext - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - ( - preservesValue = true and - localFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalStateStep(node1, state1, node2, state2, config) - ) and - exists(ap) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand1/6; - - predicate flowIntoCall = flowIntoCallNodeCand1/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - expectsContentEx(node, c) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - PrevStage::revFlowState(state, pragma[only_bind_into](config)) and - exists(ap) and - not stateBarrier(node, state, config) and - ( - notExpectsContent(node) - or - ap = true and - expectsContentCand(node, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage2 implements StageSig { - import MkStage::Stage -} - -pragma[nomagic] -private predicate flowOutOfCallNodeCand2( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -pragma[nomagic] -private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -private module LocalFlowBigStep { - /** - * A node where some checking is required, and hence the big-step relation - * is not allowed to step over. - */ - private class FlowCheckNode extends NodeEx { - FlowCheckNode() { - castNode(this.asNode()) or - clearsContentCached(this.asNode(), _) or - expectsContentCached(this.asNode(), _) - } - } - - /** - * Holds if `node` can be the first node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowEntry(NodeEx node, FlowState state, Configuration config) { - Stage2::revFlow(node, state, config) and - ( - sourceNode(node, state, config) - or - jumpStep(_, node, config) - or - additionalJumpStep(_, node, config) - or - additionalJumpStateStep(_, _, node, state, config) - or - node instanceof ParamNodeEx - or - node.asNode() instanceof OutNodeExt - or - Stage2::storeStepCand(_, _, _, node, _, config) - or - Stage2::readStepCand(_, _, node, config) - or - node instanceof FlowCheckNode - or - exists(FlowState s | - additionalLocalStateStep(_, s, node, state, config) and - s != state - ) - ) - } - - /** - * Holds if `node` can be the last node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowExit(NodeEx node, FlowState state, Configuration config) { - exists(NodeEx next | Stage2::revFlow(next, state, config) | - jumpStep(node, next, config) or - additionalJumpStep(node, next, config) or - flowIntoCallNodeCand2(_, node, next, _, config) or - flowOutOfCallNodeCand2(_, node, _, next, _, config) or - Stage2::storeStepCand(node, _, _, next, _, config) or - Stage2::readStepCand(node, _, next, config) - ) - or - exists(NodeEx next, FlowState s | Stage2::revFlow(next, s, config) | - additionalJumpStateStep(node, state, next, s, config) - or - additionalLocalStateStep(node, state, next, s, config) and - s != state - ) - or - Stage2::revFlow(node, state, config) and - node instanceof FlowCheckNode - or - sinkNode(node, state, config) - } - - pragma[noinline] - private predicate additionalLocalFlowStepNodeCand2( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, Configuration config - ) { - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 and - Stage2::revFlow(node1, pragma[only_bind_into](state1), false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false, - pragma[only_bind_into](config)) - or - additionalLocalStateStep(node1, state1, node2, state2, config) and - Stage2::revFlow(node1, state1, false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, state2, false, pragma[only_bind_into](config)) - } - - /** - * Holds if the local path from `node1` to `node2` is a prefix of a maximal - * subsequence of local flow steps in a dataflow path. - * - * This is the transitive closure of `[additional]localFlowStep` beginning - * at `localFlowEntry`. - */ - pragma[nomagic] - private predicate localFlowStepPlus( - NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, - Configuration config, LocalCallContext cc - ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and - ( - localFlowEntry(node1, pragma[only_bind_into](state), pragma[only_bind_into](config)) and - ( - localFlowStepNodeCand1(node1, node2, config) and - preservesValue = true and - t = node1.getDataFlowType() and // irrelevant dummy value - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - or - additionalLocalFlowStepNodeCand2(node1, state, node2, state, config) and - preservesValue = false and - t = node2.getDataFlowType() - ) and - node1 != node2 and - cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) - or - exists(NodeEx mid | - localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, - pragma[only_bind_into](config), cc) and - localFlowStepNodeCand1(mid, node2, config) and - not mid instanceof FlowCheckNode and - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - ) - or - exists(NodeEx mid | - localFlowStepPlus(node1, state, mid, _, _, pragma[only_bind_into](config), cc) and - additionalLocalFlowStepNodeCand2(mid, state, node2, state, config) and - not mid instanceof FlowCheckNode and - preservesValue = false and - t = node2.getDataFlowType() - ) - ) - } - - /** - * Holds if `node1` can step to `node2` in one or more local steps and this - * path can occur as a maximal subsequence of local steps in a dataflow path. - */ - pragma[nomagic] - predicate localFlowBigStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - DataFlowType t, Configuration config, LocalCallContext callContext - ) { - localFlowStepPlus(node1, state1, node2, preservesValue, t, config, callContext) and - localFlowExit(node2, state1, config) and - state1 = state2 - or - additionalLocalFlowStepNodeCand2(node1, state1, node2, state2, config) and - state1 != state2 and - preservesValue = false and - t = node2.getDataFlowType() and - callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) - } -} - -private import LocalFlowBigStep - -private module Stage3Param implements MkStage::StageParam { - private module PrevStage = Stage2; - - class Ap = ApproxAccessPathFront; - - class ApNil = ApproxAccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } - - class ApHeadContent = ContentApprox; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - predicate projectToHeadContent = getContentApprox/1; - - class ApOption = ApproxAccessPathFrontOption; - - ApOption apNone() { result = TApproxAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } - - import BooleanCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApproxAccessPathFrontNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand2/6; - - predicate flowIntoCall = flowIntoCallNodeCand2/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getAHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage3 implements StageSig { - import MkStage::Stage -} - -private module Stage4Param implements MkStage::StageParam { - private module PrevStage = Stage3; - - class Ap = AccessPathFront; - - class ApNil = AccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathFrontOption; - - ApOption apNone() { result = TAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } - - import BooleanCallContext - - pragma[nomagic] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) and - exists(lcc) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clearSet(NodeEx node, ContentSet c, Configuration config) { - PrevStage::revFlow(node, config) and - clearsContentCached(node.asNode(), c) - } - - pragma[nomagic] - private predicate clearContent(NodeEx node, Content c, Configuration config) { - exists(ContentSet cs | - PrevStage::readStepCand(_, pragma[only_bind_into](c), _, pragma[only_bind_into](config)) and - c = cs.getAReadContent() and - clearSet(node, cs, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clear(NodeEx node, Ap ap, Configuration config) { - clearContent(node, ap.getHead().getContent(), config) - } - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - not clear(node, ap, config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage4 implements StageSig { - import MkStage::Stage -} - -/** - * Holds if `argApf` is recorded as the summary context for flow reaching `node` - * and remains relevant for the following pruning stage. - */ -private predicate flowCandSummaryCtx( - NodeEx node, FlowState state, AccessPathFront argApf, Configuration config -) { - exists(AccessPathFront apf | - Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf, config) and - Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf, - config) - ) -} - -/** - * Holds if a length 2 access path approximation with the head `tc` is expected - * to be expensive. - */ -private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { - exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf, config)) and - nodes = - strictcount(NodeEx n, FlowState state | - Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - or - flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - ) and - accessPathApproxCostLimits(apLimit, tupleLimit) and - apLimit < tails and - tupleLimit < (tails - 1) * nodes and - not tc.forceHighPrecision() - ) -} - -private newtype TAccessPathApprox = - TNil(DataFlowType t) or - TConsNil(TypedContent tc, DataFlowType t) { - Stage4::consCand(tc, TFrontNil(t), _) and - not expensiveLen2unfolding(tc, _) - } or - TConsCons(TypedContent tc1, TypedContent tc2, int len) { - Stage4::consCand(tc1, TFrontHead(tc2), _) and - len in [2 .. accessPathLimit()] and - not expensiveLen2unfolding(tc1, _) - } or - TCons1(TypedContent tc, int len) { - len in [1 .. accessPathLimit()] and - expensiveLen2unfolding(tc, _) - } - -/** - * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only - * the first two elements of the list and its length are tracked. If data flows - * from a source to a given node with a given `AccessPathApprox`, this indicates - * the sequence of dereference operations needed to get from the value in the node - * to the tracked object. The final type indicates the type of the tracked object. - */ -abstract private class AccessPathApprox extends TAccessPathApprox { - abstract string toString(); - - abstract TypedContent getHead(); - - abstract int len(); - - abstract DataFlowType getType(); - - abstract AccessPathFront getFront(); - - /** Gets the access path obtained by popping `head` from this path, if any. */ - abstract AccessPathApprox pop(TypedContent head); -} - -private class AccessPathApproxNil extends AccessPathApprox, TNil { - private DataFlowType t; - - AccessPathApproxNil() { this = TNil(t) } - - override string toString() { result = concat(": " + ppReprType(t)) } - - override TypedContent getHead() { none() } - - override int len() { result = 0 } - - override DataFlowType getType() { result = t } - - override AccessPathFront getFront() { result = TFrontNil(t) } - - override AccessPathApprox pop(TypedContent head) { none() } -} - -abstract private class AccessPathApproxCons extends AccessPathApprox { } - -private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { - private TypedContent tc; - private DataFlowType t; - - AccessPathApproxConsNil() { this = TConsNil(tc, t) } - - override string toString() { - // The `concat` becomes "" if `ppReprType` has no result. - result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) - } - - override TypedContent getHead() { result = tc } - - override int len() { result = 1 } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } -} - -private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { - private TypedContent tc1; - private TypedContent tc2; - private int len; - - AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - - override string toString() { - if len = 2 - then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" - else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc1 } - - override int len() { result = len } - - override DataFlowType getType() { result = tc1.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc1) } - - override AccessPathApprox pop(TypedContent head) { - head = tc1 and - ( - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - } -} - -private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { - private TypedContent tc; - private int len; - - AccessPathApproxCons1() { this = TCons1(tc, len) } - - override string toString() { - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc } - - override int len() { result = len } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { - head = tc and - ( - exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2), _) | - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - or - exists(DataFlowType t | - len = 1 and - Stage4::consCand(tc, TFrontNil(t), _) and - result = TNil(t) - ) - ) - } -} - -/** Gets the access path obtained by popping `tc` from `ap`, if any. */ -private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } - -/** Gets the access path obtained by pushing `tc` onto `ap`. */ -private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } - -private newtype TAccessPathApproxOption = - TAccessPathApproxNone() or - TAccessPathApproxSome(AccessPathApprox apa) - -private class AccessPathApproxOption extends TAccessPathApproxOption { - string toString() { - this = TAccessPathApproxNone() and result = "" - or - this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) - } -} - -private module Stage5Param implements MkStage::StageParam { - private module PrevStage = Stage4; - - class Ap = AccessPathApprox; - - class ApNil = AccessPathApproxNil; - - pragma[nomagic] - PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathApproxOption; - - ApOption apNone() { result = TAccessPathApproxNone() } - - ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } - - import Level1CallContext - import LocalCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, lcc) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { any() } - - // Type checking is not necessary here as it has already been done in stage 3. - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage5 = MkStage::Stage; - -bindingset[conf, result] -private Configuration unbindConf(Configuration conf) { - exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c)) -} - -pragma[nomagic] -private predicate nodeMayUseSummary0( - NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(AccessPathApprox apa0 | - Stage5::parameterMayFlowThrough(p, _, _) and - Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0, config) and - Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), - TAccessPathApproxSome(apa), apa0, config) - ) -} - -pragma[nomagic] -private predicate nodeMayUseSummary( - NodeEx n, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::parameterMayFlowThrough(p, apa, config) and - nodeMayUseSummary0(n, p, state, apa, config) - ) -} - -private newtype TSummaryCtx = - TSummaryCtxNone() or - TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { - exists(Configuration config | - Stage5::parameterMayFlowThrough(p, ap.getApprox(), config) and - Stage5::revFlow(p, state, _, config) - ) - } - -/** - * A context for generating flow summaries. This represents flow entry through - * a specific parameter with an access path of a specific shape. - * - * Summaries are only created for parameters that may flow through. - */ -abstract private class SummaryCtx extends TSummaryCtx { - abstract string toString(); -} - -/** A summary context from which no flow summary can be generated. */ -private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { - override string toString() { result = "" } -} - -/** A summary context from which a flow summary can be generated. */ -private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParamNodeEx p; - private FlowState s; - private AccessPath ap; - - SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } - - ParameterPosition getParameterPos() { p.isParameterOf(_, result) } - - ParamNodeEx getParamNode() { result = p } - - override string toString() { result = p + ": " + ap } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** - * Gets the number of length 2 access path approximations that correspond to `apa`. - */ -private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { - exists(TypedContent tc, int len | - tc = apa.getHead() and - len = apa.len() and - result = - strictcount(AccessPathFront apf | - Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), - config) - ) - ) -} - -private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = - strictcount(NodeEx n, FlowState state | - Stage5::revFlow(n, state, apa, config) or nodeMayUseSummary(n, state, apa, config) - ) -} - -/** - * Holds if a length 2 access path approximation matching `apa` is expected - * to be expensive. - */ -private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = count1to2unfold(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - apLimit < aps and - tupleLimit < (aps - 1) * nodes - ) -} - -private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { - exists(TypedContent head | - apa.pop(head) = result and - Stage5::consCand(head, result, config) - ) -} - -/** - * Holds with `unfold = false` if a precise head-tail representation of `apa` is - * expected to be expensive. Holds with `unfold = true` otherwise. - */ -private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { - if apa.getHead().forceHighPrecision() - then unfold = true - else - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = countPotentialAps(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true - ) -} - -/** - * Gets the number of `AccessPath`s that correspond to `apa`. - */ -pragma[assume_small_delta] -private int countAps(AccessPathApprox apa, Configuration config) { - evalUnfold(apa, false, config) and - result = 1 and - (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) - or - evalUnfold(apa, false, config) and - result = count1to2unfold(apa, config) and - not expensiveLen1to2unfolding(apa, config) - or - evalUnfold(apa, true, config) and - result = countPotentialAps(apa, config) -} - -/** - * Gets the number of `AccessPath`s that would correspond to `apa` assuming - * that it is expanded to a precise head-tail representation. - */ -language[monotonicAggregates] -pragma[assume_small_delta] -private int countPotentialAps(AccessPathApprox apa, Configuration config) { - apa instanceof AccessPathApproxNil and result = 1 - or - result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) -} - -private newtype TAccessPath = - TAccessPathNil(DataFlowType t) or - TAccessPathCons(TypedContent head, AccessPath tail) { - exists(AccessPathApproxCons apa | - not evalUnfold(apa, false, _) and - head = apa.getHead() and - tail.getApprox() = getATail(apa, _) - ) - } or - TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - not expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head1 = apa.getHead() and - head2 = getATail(apa, _).getHead() - ) - } or - TAccessPathCons1(TypedContent head, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head = apa.getHead() - ) - } - -private newtype TPathNode = - pragma[assume_small_delta] - TPathNodeMid( - NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config - ) { - // A PathNode is introduced by a source ... - Stage5::revFlow(node, state, config) and - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - or - // ... or a step from an existing PathNode to another node. - exists(PathNodeMid mid | - pathStep(mid, node, state, cc, sc, ap) and - pragma[only_bind_into](config) = mid.getConfiguration() and - Stage5::revFlow(node, state, ap.getApprox(), pragma[only_bind_into](config)) - ) - } or - TPathNodeSink(NodeEx node, FlowState state, Configuration config) { - exists(PathNodeMid sink | - sink.isAtSink() and - node = sink.getNodeEx() and - state = sink.getState() and - config = sink.getConfiguration() - ) - } or - TPathNodeSourceGroup(string sourceGroup, Configuration config) { - exists(PathNodeImpl source | - sourceGroup = source.getSourceGroup() and - config = source.getConfiguration() - ) - } or - TPathNodeSinkGroup(string sinkGroup, Configuration config) { - exists(PathNodeSink sink | - sinkGroup = sink.getSinkGroup() and - config = sink.getConfiguration() - ) - } - -/** - * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a - * source to a given node with a given `AccessPath`, this indicates the sequence - * of dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ -private class AccessPath extends TAccessPath { - /** Gets the head of this access path, if any. */ - abstract TypedContent getHead(); - - /** Gets the tail of this access path, if any. */ - abstract AccessPath getTail(); - - /** Gets the front of this access path. */ - abstract AccessPathFront getFront(); - - /** Gets the approximation of this access path. */ - abstract AccessPathApprox getApprox(); - - /** Gets the length of this access path. */ - abstract int length(); - - /** Gets a textual representation of this access path. */ - abstract string toString(); - - /** Gets the access path obtained by popping `tc` from this access path, if any. */ - final AccessPath pop(TypedContent tc) { - result = this.getTail() and - tc = this.getHead() - } - - /** Gets the access path obtained by pushing `tc` onto this access path. */ - final AccessPath push(TypedContent tc) { this = result.pop(tc) } -} - -private class AccessPathNil extends AccessPath, TAccessPathNil { - private DataFlowType t; - - AccessPathNil() { this = TAccessPathNil(t) } - - DataFlowType getType() { result = t } - - override TypedContent getHead() { none() } - - override AccessPath getTail() { none() } - - override AccessPathFrontNil getFront() { result = TFrontNil(t) } - - override AccessPathApproxNil getApprox() { result = TNil(t) } - - override int length() { result = 0 } - - override string toString() { result = concat(": " + ppReprType(t)) } -} - -private class AccessPathCons extends AccessPath, TAccessPathCons { - private TypedContent head; - private AccessPath tail; - - AccessPathCons() { this = TAccessPathCons(head, tail) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { result = tail } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - pragma[assume_small_delta] - override AccessPathApproxCons getApprox() { - result = TConsNil(head, tail.(AccessPathNil).getType()) - or - result = TConsCons(head, tail.getHead(), this.length()) - or - result = TCons1(head, this.length()) - } - - pragma[assume_small_delta] - override int length() { result = 1 + tail.length() } - - private string toStringImpl(boolean needsSuffix) { - exists(DataFlowType t | - tail = TAccessPathNil(t) and - needsSuffix = false and - result = head.toString() + "]" + concat(" : " + ppReprType(t)) - ) - or - result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) - or - exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | - result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true - or - result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false - ) - or - exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | - result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true - or - result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false - ) - } - - override string toString() { - result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" - or - result = "[" + this.toStringImpl(false) - } -} - -private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { - private TypedContent head1; - private TypedContent head2; - private int len; - - AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } - - override TypedContent getHead() { result = head1 } - - override AccessPath getTail() { - Stage5::consCand(head1, result.getApprox(), _) and - result.getHead() = head2 and - result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head1) } - - override AccessPathApproxCons getApprox() { - result = TConsCons(head1, head2, len) or - result = TCons1(head1, len) - } - - override int length() { result = len } - - override string toString() { - if len = 2 - then result = "[" + head1.toString() + ", " + head2.toString() + "]" - else - result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" - } -} - -private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { - private TypedContent head; - private int len; - - AccessPathCons1() { this = TAccessPathCons1(head, len) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { - Stage5::consCand(head, result.getApprox(), _) and result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - - override int length() { result = len } - - override string toString() { - if len = 1 - then result = "[" + head.toString() + "]" - else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" - } -} - -abstract private class PathNodeImpl extends TPathNode { - /** Gets the `FlowState` of this node. */ - abstract FlowState getState(); - - /** Gets the associated configuration. */ - abstract Configuration getConfiguration(); - - /** Holds if this node is a source. */ - abstract predicate isSource(); - - abstract PathNodeImpl getASuccessorImpl(); - - private PathNodeImpl getASuccessorIfHidden() { - this.isHidden() and - result = this.getASuccessorImpl() - } - - pragma[nomagic] - private PathNodeImpl getANonHiddenSuccessor0() { - result = this.getASuccessorIfHidden*() and - not result.isHidden() - } - - final PathNodeImpl getANonHiddenSuccessor() { - result = this.getASuccessorImpl().getANonHiddenSuccessor0() and - not this.isHidden() - } - - abstract NodeEx getNodeEx(); - - predicate isHidden() { - not this.getConfiguration().includeHiddenNodes() and - ( - hiddenNode(this.getNodeEx().asNode()) and - not this.isSource() and - not this instanceof PathNodeSink - or - this.getNodeEx() instanceof TNodeImplicitRead - ) - } - - string getSourceGroup() { - this.isSource() and - this.getConfiguration().sourceGrouping(this.getNodeEx().asNode(), result) + not singleConfiguration() and + getConfig(state1).isAdditionalFlowStep(node1, node2) and + state2 = state1 } - predicate isFlowSource() { - this.isSource() and not exists(this.getSourceGroup()) - or - this instanceof PathNodeSourceGroup + predicate allowImplicitRead(Node node, ContentSet c) { + any(Configuration config).allowImplicitRead(node, c) } - predicate isFlowSink() { - this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or - this instanceof PathNodeSinkGroup - } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } - private string ppAp() { - this instanceof PathNodeSink and result = "" - or - exists(string s | s = this.(PathNodeMid).getAp().toString() | - if s = "" then result = "" else result = " " + s - ) - } + FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } - private string ppCtx() { - this instanceof PathNodeSink and result = "" - or - result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + predicate sourceGrouping(Node source, string sourceGroup) { + any(Configuration config).sourceGrouping(source, sourceGroup) } - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + predicate sinkGrouping(Node sink, string sinkGroup) { + any(Configuration config).sinkGrouping(sink, sinkGroup) } -} - -/** Holds if `n` can reach a sink. */ -private predicate directReach(PathNodeImpl n) { - n instanceof PathNodeSink or - n instanceof PathNodeSinkGroup or - directReach(n.getANonHiddenSuccessor()) -} - -/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ -private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } -/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { - n1.getANonHiddenSuccessor() = n2 and directReach(n2) + predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() } } -private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) +private import Impl as I +import I /** * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. */ -class PathNode instanceof PathNodeImpl { - PathNode() { reach(this) } - +class PathNode instanceof I::PathNode { /** Gets a textual representation of this element. */ final string toString() { result = super.toString() } @@ -3406,1548 +358,39 @@ class PathNode instanceof PathNodeImpl { } /** Gets the underlying `Node`. */ - final Node getNode() { super.getNodeEx().projectToNode() = result } + final Node getNode() { result = super.getNode() } /** Gets the `FlowState` of this node. */ - final FlowState getState() { result = super.getState() } + final FlowState getState() { result = getState(super.getState()) } /** Gets the associated configuration. */ - final Configuration getConfiguration() { result = super.getConfiguration() } + final Configuration getConfiguration() { result = getConfig(super.getState()) } /** Gets a successor of this node, if any. */ - final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } + final PathNode getASuccessor() { result = super.getASuccessor() } /** Holds if this node is a source. */ final predicate isSource() { super.isSource() } /** Holds if this node is a grouping of source nodes. */ - final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group, _) } + final predicate isSourceGroup(string group) { super.isSourceGroup(group) } /** Holds if this node is a grouping of sink nodes. */ - final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group, _) } + final predicate isSinkGroup(string group) { super.isSinkGroup(group) } } -/** - * Provides the query predicates needed to include a graph in a path-problem query. - */ -module PathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } - - /** Holds if `n` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode n, string key, string val) { - key = "semmle.label" and val = n.toString() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { - Subpaths::subpaths(arg, par, ret, out) - } -} - -/** - * An intermediate flow graph node. This is a triple consisting of a `Node`, - * a `CallContext`, and a `Configuration`. - */ -private class PathNodeMid extends PathNodeImpl, TPathNodeMid { - NodeEx node; - FlowState state; - CallContext cc; - SummaryCtx sc; - AccessPath ap; - Configuration config; - - PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - SummaryCtx getSummaryCtx() { result = sc } - - AccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - private PathNodeMid getSuccMid() { - pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx(), result.getAp()) and - result.getConfiguration() = unbindConf(this.getConfiguration()) - } - - override PathNodeImpl getASuccessorImpl() { - // an intermediate step to another intermediate node - result = this.getSuccMid() - or - // a final step to a sink - result = this.getSuccMid().projectToSink() - } - - override predicate isSource() { - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - } - - predicate isAtSink() { - sinkNode(node, state, config) and - ap instanceof AccessPathNil and - if hasSinkCallCtx(config) - then - // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` - // is exactly what we need to check. This also implies - // `sc instanceof SummaryCtxNone`. - // For `FeatureEqualSourceSinkCallContext` the initial call context was - // set to `CallContextSomeCall` and jumps are disallowed, so - // `cc instanceof CallContextNoCall` never holds. On the other hand, - // in this case there's never any need to enter a call except to identify - // a summary, so the condition in `pathIntoCallable` enforces this, which - // means that `sc instanceof SummaryCtxNone` holds if and only if we are - // in the call context of the source. - sc instanceof SummaryCtxNone or - cc instanceof CallContextNoCall - else any() - } - - PathNodeSink projectToSink() { - this.isAtSink() and - result.getNodeEx() = node and - result.getState() = state and - result.getConfiguration() = unbindConf(config) - } -} - -/** - * A flow graph node corresponding to a sink. This is disjoint from the - * intermediate nodes in order to uniquely correspond to a given sink by - * excluding the `CallContext`. - */ -private class PathNodeSink extends PathNodeImpl, TPathNodeSink { - NodeEx node; - FlowState state; - Configuration config; - - PathNodeSink() { this = TPathNodeSink(node, state, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result = TPathNodeSinkGroup(this.getSinkGroup(), config) - } - - override predicate isSource() { sourceNode(node, state, config) } - - string getSinkGroup() { config.sinkGrouping(node.asNode(), result) } -} - -private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { - string sourceGroup; - Configuration config; - - PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result.getSourceGroup() = sourceGroup and - result.getConfiguration() = config - } - - override predicate isSource() { none() } - - override string toString() { result = sourceGroup } - - override 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 - } -} - -private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { - string sinkGroup; - Configuration config; - - PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { none() } - - override predicate isSource() { none() } - - override string toString() { result = sinkGroup } - - override 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 - } -} - -private predicate pathNode( - PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, - Configuration conf, LocalCallContext localCC -) { - midnode = mid.getNodeEx() and - state = mid.getState() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - midnode.getEnclosingCallable()) and - ap = mid.getAp() -} - -/** - * Holds if data may flow from `mid` to `node`. The last step in or out of - * a callable is recorded by `cc`. - */ -pragma[assume_small_delta] -pragma[nomagic] -private predicate pathStep( - PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap -) { - exists(NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC | - pathNode(mid, midnode, state0, cc, sc, ap, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, true, _, conf, localCC) - ) - or - exists( - AccessPath ap0, NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC - | - pathNode(mid, midnode, state0, cc, sc, ap0, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), conf, - localCC) and - ap0 instanceof AccessPathNil - ) - or - jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - ap = mid.getAp() - or - additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, mid.getConfiguration()) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - pathIntoCallable(mid, node, state, _, cc, sc, _, _) and ap = mid.getAp() - or - pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone - or - pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() -} - -pragma[nomagic] -private predicate pathReadStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - tc = ap0.getHead() and - Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -pragma[nomagic] -private predicate pathStoreStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -private predicate pathOutOfCallable0( - PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa, - Configuration config -) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - apa = mid.getAp().getApprox() and - config = mid.getConfiguration() -} - -pragma[nomagic] -private predicate pathOutOfCallable1( - PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPathApprox apa, Configuration config -) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - pathOutOfCallable0(mid, pos, state, innercc, apa, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() +private predicate hasFlow(Node source, Node sink, Configuration config) { + exists(PathNode source0, PathNode sink0 | + hasFlowPath(source0, sink0, config) and + source0.getNode() = source and + sink0.getNode() = sink ) } -pragma[noinline] -private NodeEx getAnOutNodeFlow( - ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config -) { - result.asNode() = kind.getAnOutNode(call) and - Stage5::revFlow(result, _, apa, config) +private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) { + hasFlowPath(source, sink) and source.getConfiguration() = config } -/** - * Holds if data may flow from `mid` to `out`. The last step of this path - * is a return from a callable and is recorded by `cc`, if needed. - */ -pragma[noinline] -private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { - exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | - pathOutOfCallable1(mid, call, kind, state, cc, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} +private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) } -/** - * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. - */ -pragma[noinline] -private predicate pathIntoArg( - PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(ArgNodeEx arg, ArgumentPosition apos | - pathNode(mid, arg, state, cc, _, ap, config, _) and - arg.asNode().(ArgNode).argumentOf(call, apos) and - apa = ap.getApprox() and - parameterMatch(ppos, apos) - ) -} - -pragma[nomagic] -private predicate parameterCand( - DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::revFlow(p, _, apa, config) and - p.isParameterOf(callable, pos) - ) -} - -pragma[nomagic] -private predicate pathIntoCallable0( - PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, AccessPath ap, Configuration config -) { - exists(AccessPathApprox apa | - pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - callable = resolveCall(call, outercc) and - parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa), - pragma[only_bind_into](config)) - ) -} - -/** - * Holds if data may flow from `mid` to `p` through `call`. The contexts - * before and after entering the callable are `outercc` and `innercc`, - * respectively. - */ -pragma[nomagic] -private predicate pathIntoCallable( - PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, - SummaryCtx sc, DataFlowCall call, Configuration config -) { - exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | - pathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - ( - sc = TSummaryCtxSome(p, state, ap) - or - not exists(TSummaryCtxSome(p, state, ap)) and - sc = TSummaryCtxNone() and - // When the call contexts of source and sink needs to match then there's - // never any reason to enter a callable except to find a summary. See also - // the comment in `PathNodeMid::isAtSink`. - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) -} - -/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ -pragma[nomagic] -private predicate paramFlowsThrough( - ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, - AccessPathApprox apa, Configuration config -) { - exists(RetNodeEx ret | - pathNode(_, ret, state, cc, sc, ap, config, _) and - kind = ret.getKind() and - apa = ap.getApprox() and - parameterFlowThroughAllowed(sc.getParamNode(), kind) - ) -} - -pragma[nomagic] -private predicate pathThroughCallable0( - DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(CallContext innercc, SummaryCtx sc | - pathIntoCallable(mid, _, _, cc, innercc, sc, call, config) and - paramFlowsThrough(kind, state, innercc, sc, ap, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` through a callable to the node `out`. - * The context `cc` is restored to its value prior to entering the callable. - */ -pragma[noinline] -private predicate pathThroughCallable( - PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap -) { - exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config | - pathThroughCallable0(call, mid, kind, state, cc, ap, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -private module Subpaths { - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths01( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - exists(Configuration config | - pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and - pathIntoCallable(arg, par, _, _, innercc, sc, _, config) and - paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, - pragma[only_bind_into](apout), _, unbindConf(config)) and - not arg.isHidden() - ) - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `sout`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths02( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and - out.asNode() = kind.getAnOutNode(_) - } - - pragma[nomagic] - private Configuration getPathNodeConf(PathNodeImpl n) { result = n.getConfiguration() } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple. - */ - pragma[nomagic] - private predicate subpaths03( - PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, AccessPath apout - ) { - exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | - subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and - pathNode(ret, retnode, sout, innercc, sc, apout, unbindConf(getPathNodeConf(arg)), _) and - kind = retnode.getKind() - ) - } - - private PathNodeImpl localStepToHidden(PathNodeImpl n) { - n.getASuccessorImpl() = result and - result.isHidden() and - exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | - localFlowBigStep(n1, _, n2, _, _, _, _, _) or - store(n1, _, n2, _, _) or - readSet(n1, _, n2, _) - ) - } - - pragma[nomagic] - private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { - succ = pred.getANonHiddenSuccessor() and - succNode = succ.getNodeEx() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { - exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | - pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and - subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and - hasSuccessor(pragma[only_bind_into](arg), par, p) and - not ret.isHidden() and - pathNode(out0, o, sout, _, _, apout, _, _) - | - out = out0 or out = out0.projectToSink() - ) - } - - /** - * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. - */ - predicate retReach(PathNodeImpl n) { - exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) - or - exists(PathNodeImpl mid | - retReach(mid) and - n.getANonHiddenSuccessor() = mid and - not subpaths(_, mid, _, _) - ) - } -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -private predicate hasFlowPath( - PathNodeImpl flowsource, PathNodeImpl flowsink, Configuration configuration -) { - flowsource.isFlowSource() and - flowsource.getConfiguration() = configuration and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.isFlowSink() -} - -private predicate flowsTo( - PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink, - Configuration configuration -) { - flowsource.isSource() and - flowsource.getConfiguration() = configuration and - flowsource.getNodeEx().asNode() = source and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.getNodeEx().asNode() = sink -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -predicate flowsTo(Node source, Node sink, Configuration configuration) { - flowsTo(_, _, source, sink, configuration) -} - -private predicate finalStats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples -) { - fwd = true and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and - tuples = count(PathNodeImpl pn) - or - fwd = false and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and - tuples = count(PathNode pn) -} - -/** - * INTERNAL: Only for debugging. - * - * Calculates per-stage metrics for data flow. - */ -predicate stageStats( - int n, string stage, int nodes, int fields, int conscand, int states, int tuples, - Configuration config -) { - stage = "1 Fwd" and - n = 10 and - Stage1::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "1 Rev" and - n = 15 and - Stage1::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Fwd" and - n = 20 and - Stage2::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Rev" and - n = 25 and - Stage2::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Fwd" and - n = 30 and - Stage3::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Rev" and - n = 35 and - Stage3::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Fwd" and - n = 40 and - Stage4::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Rev" and - n = 45 and - Stage4::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Fwd" and - n = 50 and - Stage5::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Rev" and - n = 55 and - Stage5::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) - or - stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) -} - -private module FlowExploration { - private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - jumpStep(node1, node2, config) - or - additionalJumpStep(node1, node2, config) - or - additionalJumpStateStep(node1, _, node2, _, config) - or - // flow into callable - viableParamArgEx(_, node2, node1) - or - // flow out of a callable - viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) - | - c1 = node1.getEnclosingCallable() and - c2 = node2.getEnclosingCallable() and - c1 != c2 - ) - } - - private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSource(n) or config.isSource(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSrc(mid, config) and callableStep(mid, c, config) - ) - } - - private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSink(n) or config.isSink(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSink(mid, config) and callableStep(c, mid, config) - ) - } - - private newtype TCallableExt = - TCallable(DataFlowCallable c, Configuration config) { - interestingCallableSrc(c, config) or - interestingCallableSink(c, config) - } or - TCallableSrc() or - TCallableSink() - - private predicate callableExtSrc(TCallableSrc src) { any() } - - private predicate callableExtSink(TCallableSink sink) { any() } - - private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { - exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | - callableStep(c1, c2, config) and - ce1 = TCallable(c1, pragma[only_bind_into](config)) and - ce2 = TCallable(c2, pragma[only_bind_into](config)) - ) - or - exists(Node n, Configuration config | - ce1 = TCallableSrc() and - (config.isSource(n) or config.isSource(n, _)) and - ce2 = TCallable(getNodeEnclosingCallable(n), config) - ) - or - exists(Node n, Configuration config | - ce2 = TCallableSink() and - (config.isSink(n) or config.isSink(n, _)) and - ce1 = TCallable(getNodeEnclosingCallable(n), config) - ) - } - - private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { - callableExtStepFwd(ce2, ce1) - } - - private int distSrcExt(TCallableExt c) = - shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) - - private int distSinkExt(TCallableExt c) = - shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) - - private int distSrc(DataFlowCallable c, Configuration config) { - result = distSrcExt(TCallable(c, config)) - 1 - } - - private int distSink(DataFlowCallable c, Configuration config) { - result = distSinkExt(TCallable(c, config)) - 1 - } - - private newtype TPartialAccessPath = - TPartialNil(DataFlowType t) or - TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first - * element of the list and its length are tracked. If data flows from a source to - * a given node with a given `AccessPath`, this indicates the sequence of - * dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ - private class PartialAccessPath extends TPartialAccessPath { - abstract string toString(); - - TypedContent getHead() { this = TPartialCons(result, _) } - - int len() { - this = TPartialNil(_) and result = 0 - or - this = TPartialCons(_, result) - } - - DataFlowType getType() { - this = TPartialNil(result) - or - exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) - } - } - - private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { - override string toString() { - exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) - } - } - - private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { - override string toString() { - exists(TypedContent tc, int len | this = TPartialCons(tc, len) | - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private newtype TRevPartialAccessPath = - TRevPartialNil() or - TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `Content`s, but only the first - * element of the list and its length are tracked. - */ - private class RevPartialAccessPath extends TRevPartialAccessPath { - abstract string toString(); - - Content getHead() { this = TRevPartialCons(result, _) } - - int len() { - this = TRevPartialNil() and result = 0 - or - this = TRevPartialCons(_, result) - } - } - - private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { - override string toString() { result = "" } - } - - private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { - override string toString() { - exists(Content c, int len | this = TRevPartialCons(c, len) | - if len = 1 - then result = "[" + c.toString() + "]" - else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private predicate relevantState(FlowState state) { - sourceNode(_, state, _) or - sinkNode(_, state, _) or - additionalLocalStateStep(_, state, _, _, _) or - additionalLocalStateStep(_, _, _, state, _) or - additionalJumpStateStep(_, state, _, _, _) or - additionalJumpStateStep(_, _, _, state, _) - } - - private newtype TSummaryCtx1 = - TSummaryCtx1None() or - TSummaryCtx1Param(ParamNodeEx p) - - private newtype TSummaryCtx2 = - TSummaryCtx2None() or - TSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TSummaryCtx3 = - TSummaryCtx3None() or - TSummaryCtx3Some(PartialAccessPath ap) - - private newtype TRevSummaryCtx1 = - TRevSummaryCtx1None() or - TRevSummaryCtx1Some(ReturnPosition pos) - - private newtype TRevSummaryCtx2 = - TRevSummaryCtx2None() or - TRevSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TRevSummaryCtx3 = - TRevSummaryCtx3None() or - TRevSummaryCtx3Some(RevPartialAccessPath ap) - - private newtype TPartialPathNode = - TPartialPathNodeFwd( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = TPartialNil(node.getDataFlowType()) and - exists(config.explorationLimit()) - or - partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap, config) and - distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() - } or - TPartialPathNodeRev( - NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, - RevPartialAccessPath ap, Configuration config - ) { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() and - exists(config.explorationLimit()) - or - revPartialPathStep(_, node, state, sc1, sc2, sc3, ap, config) and - not clearsContentEx(node, ap.getHead()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead()) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() - } - - pragma[nomagic] - private predicate partialPathNodeMk0( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap, config) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - not clearsContentEx(node, ap.getHead().getContent()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead().getContent()) - ) and - if node.asNode() instanceof CastingNode - then compatibleTypes(node.getDataFlowType(), ap.getType()) - else any() - } - - /** - * A `Node` augmented with a call context, an access path, and a configuration. - */ - class PartialPathNode extends TPartialPathNode { - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() - } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - /** Gets the underlying `Node`. */ - final Node getNode() { this.getNodeEx().projectToNode() = result } - - FlowState getState() { none() } - - private NodeEx getNodeEx() { - result = this.(PartialPathNodeFwd).getNodeEx() or - result = this.(PartialPathNodeRev).getNodeEx() - } - - /** Gets the associated configuration. */ - Configuration getConfiguration() { none() } - - /** Gets a successor of this node, if any. */ - PartialPathNode getASuccessor() { none() } - - /** - * Gets the approximate distance to the nearest source measured in number - * of interprocedural steps. - */ - int getSourceDistance() { - result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - /** - * Gets the approximate distance to the nearest sink measured in number - * of interprocedural steps. - */ - int getSinkDistance() { - result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - private string ppAp() { - exists(string s | - s = this.(PartialPathNodeFwd).getAp().toString() or - s = this.(PartialPathNodeRev).getAp().toString() - | - if s = "" then result = "" else result = " " + s - ) - } - - private string ppCtx() { - result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" - } - - /** Holds if this is a source in a forward-flow path. */ - predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } - - /** Holds if this is a sink in a reverse-flow path. */ - predicate isRevSink() { this.(PartialPathNodeRev).isSink() } - } - - /** - * Provides the query predicates needed to include a graph in a path-problem query. - */ - module PartialPathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } - } - - private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { - NodeEx node; - FlowState state; - CallContext cc; - TSummaryCtx1 sc1; - TSummaryCtx2 sc2; - TSummaryCtx3 sc3; - PartialAccessPath ap; - Configuration config; - - PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - TSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TSummaryCtx3 getSummaryCtx3() { result = sc3 } - - PartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeFwd getASuccessor() { - partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp(), - result.getConfiguration()) - } - - predicate isSource() { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap instanceof TPartialNil - } - } - - private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { - NodeEx node; - FlowState state; - TRevSummaryCtx1 sc1; - TRevSummaryCtx2 sc2; - TRevSummaryCtx3 sc3; - RevPartialAccessPath ap; - Configuration config; - - PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } - - RevPartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeRev getASuccessor() { - revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), - this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp(), this.getConfiguration()) - } - - predicate isSink() { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() - } - } - - private predicate partialPathStep( - PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, - TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and - ( - localFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - ) - or - jumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - partialPathStoreStep(mid, _, _, node, ap) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(PartialAccessPath ap0, TypedContent tc | - partialPathReadStep(mid, ap0, tc, node, cc, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsFwd(ap, tc, ap0, config) - ) - or - partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap, config) - or - partialPathOutOfCallable(mid, node, state, cc, ap, config) and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() - or - partialPathThroughCallable(mid, node, state, cc, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - bindingset[result, i] - private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } - - pragma[inline] - private predicate partialPathStoreStep( - PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, - PartialAccessPath ap2 - ) { - exists(NodeEx midNode, DataFlowType contentType | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - store(midNode, tc, node, contentType, mid.getConfiguration()) and - ap2.getHead() = tc and - ap2.len() = unbindInt(ap1.len() + 1) and - compatibleTypes(ap1.getType(), contentType) - ) - } - - pragma[nomagic] - private predicate apConsFwd( - PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeFwd mid | - partialPathStoreStep(mid, ap1, tc, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate partialPathReadStep( - PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc, - Configuration config - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and - ap.getHead() = tc and - pragma[only_bind_into](config) = mid.getConfiguration() and - cc = mid.getCallContext() - ) - } - - private predicate partialPathOutOfCallable0( - PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, - PartialAccessPath ap, Configuration config - ) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - ap = mid.getAp() and - config = mid.getConfiguration() - } - - pragma[nomagic] - private predicate partialPathOutOfCallable1( - PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - partialPathOutOfCallable0(mid, pos, state, innercc, ap, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) - } - - private predicate partialPathOutOfCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(ReturnKindExt kind, DataFlowCall call | - partialPathOutOfCallable1(mid, call, kind, state, cc, ap, config) - | - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[noinline] - private predicate partialPathIntoArg( - PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ArgNode arg, ArgumentPosition apos | - arg = mid.getNodeEx().asNode() and - state = mid.getState() and - cc = mid.getCallContext() and - arg.argumentOf(call, apos) and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate partialPathIntoCallable0( - PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - partialPathIntoArg(mid, pos, state, outercc, call, ap, config) and - callable = resolveCall(call, outercc) - } - - private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, - CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ParameterPosition pos, DataFlowCallable callable | - partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - sc1 = TSummaryCtx1Param(p) and - sc2 = TSummaryCtx2Some(state) and - sc3 = TSummaryCtx3Some(ap) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) - } - - pragma[nomagic] - private predicate paramFlowsThroughInPartialPath( - ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeFwd mid, RetNodeEx ret | - mid.getNodeEx() = ret and - kind = ret.getKind() and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() and - ap = mid.getAp() - ) - } - - pragma[noinline] - private predicate partialPathThroughCallable0( - DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | - partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _, config) and - paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap, config) - ) - } - - private predicate partialPathThroughCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ReturnKindExt kind | - partialPathThroughCallable0(call, mid, kind, state, cc, ap, config) and - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[nomagic] - private predicate revPartialPathStep( - PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, - TRevSummaryCtx3 sc3, RevPartialAccessPath ap, Configuration config - ) { - localFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - jumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - revPartialPathReadStep(mid, _, _, node, ap) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(RevPartialAccessPath ap0, Content c | - revPartialPathStoreStep(mid, ap0, c, node, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsRev(ap, c, ap0, config) - ) - or - exists(ParamNodeEx p | - mid.getNodeEx() = p and - viableParamArgEx(_, p, node) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - or - exists(ReturnPosition pos | - revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap, config) and - pos = getReturnPosition(node.asNode()) - ) - or - revPartialPathThroughCallable(mid, node, state, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - pragma[inline] - private predicate revPartialPathReadStep( - PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, - RevPartialAccessPath ap2 - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - read(node, c, midNode, mid.getConfiguration()) and - ap2.getHead() = c and - ap2.len() = unbindInt(ap1.len() + 1) - ) - } - - pragma[nomagic] - private predicate apConsRev( - RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeRev mid | - revPartialPathReadStep(mid, ap1, c, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathStoreStep( - PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config - ) { - exists(NodeEx midNode, TypedContent tc | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - store(node, tc, midNode, _, config) and - ap.getHead() = c and - config = mid.getConfiguration() and - tc.getContent() = c - ) - } - - pragma[nomagic] - private predicate revPartialPathIntoReturn( - PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, - TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap, - Configuration config - ) { - exists(NodeEx out | - mid.getNodeEx() = out and - mid.getState() = state and - viableReturnPosOutEx(call, pos, out) and - sc1 = TRevSummaryCtx1Some(pos) and - sc2 = TRevSummaryCtx2Some(state) and - sc3 = TRevSummaryCtx3Some(ap) and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathFlowsThrough( - ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, - TRevSummaryCtx3Some sc3, RevPartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | - mid.getNodeEx() = p and - mid.getState() = state and - p.getPosition() = ppos and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable0( - DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, - RevPartialAccessPath ap, Configuration config - ) { - exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | - revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _, config) and - revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap, config) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ArgumentPosition pos | - revPartialPathThroughCallable0(call, mid, pos, state, ap, config) and - node.asNode().(ArgNode).argumentOf(call, pos) - ) - } -} - -import FlowExploration - -private predicate partialFlow( - PartialPathNode source, PartialPathNode node, Configuration configuration -) { - source.getConfiguration() = configuration and - source.isFwdSource() and - node = source.getASuccessor+() -} - -private predicate revPartialFlow( - PartialPathNode node, PartialPathNode sink, Configuration configuration -) { - sink.getConfiguration() = configuration and - sink.isRevSink() and - node.getASuccessor+() = sink -} +predicate flowsTo = hasFlow/3; diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImplCommon.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImplCommon.qll index 5d3becc8078..f8c56d41d8d 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImplCommon.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImplCommon.qll @@ -3,15 +3,18 @@ private import DataFlowImplSpecific::Public import Cached module DataFlowImplCommonPublic { - /** A state value to track during data flow. */ - class FlowState = string; + /** Provides `FlowState = string`. */ + module FlowStateString { + /** A state value to track during data flow. */ + class FlowState = string; - /** - * The default state, which is used when the state is unspecified for a source - * or a sink. - */ - class FlowStateEmpty extends FlowState { - FlowStateEmpty() { this = "" } + /** + * The default state, which is used when the state is unspecified for a source + * or a sink. + */ + class FlowStateEmpty extends FlowState { + FlowStateEmpty() { this = "" } + } } private newtype TFlowFeature = diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImplForContentDataFlow.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImplForContentDataFlow.qll index 1b969756b09..e6bdc74cceb 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImplForContentDataFlow.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImplForContentDataFlow.qll @@ -1,16 +1,16 @@ /** - * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from - * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed - * through the `Configuration` class. This file exists in several identical - * copies, allowing queries to use multiple `Configuration` classes that depend - * on each other without introducing mutual recursion among those configurations. + * DEPRECATED: Use `Make` and `MakeWithState` instead. + * + * Provides a `Configuration` class backwards-compatible interface to the data + * flow library. */ private import DataFlowImplCommon private import DataFlowImplSpecific::Private import DataFlowImplSpecific::Public +private import DataFlowImpl import DataFlowImplCommonPublic +import FlowStateString /** * A configuration of interprocedural data flow analysis. This defines @@ -144,6 +144,10 @@ abstract class Configuration extends string { * - `FeatureEqualSourceSinkCallContext`: * Implies both of the above and additionally ensures that the entire flow * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. */ FlowFeature getAFeature() { none() } @@ -156,7 +160,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from `source` to `sink` for this configuration. */ - predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } + predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) } /** * Holds if data may flow from `source` to `sink` for this configuration. @@ -169,9 +173,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from some source to `sink` for this configuration. */ - predicate hasFlowTo(Node sink) { - sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode() - } + predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) } /** * Holds if data may flow from some source to `sink` for this configuration. @@ -179,10 +181,12 @@ abstract class Configuration extends string { predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } /** + * DEPRECATED: Use `FlowExploration` instead. + * * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` * measured in approximate number of interprocedural steps. */ - int explorationLimit() { none() } + deprecated int explorationLimit() { none() } /** * Holds if hidden nodes should be included in the data flow graph. @@ -191,49 +195,6 @@ abstract class Configuration extends string { * is not visualized (for example in a `path-problem` query). */ predicate includeHiddenNodes() { none() } - - /** - * Holds if there is a partial data flow path from `source` to `node`. The - * approximate distance between `node` and the closest source is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards sink definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sources is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - */ - final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { - partialFlow(source, node, this) and - dist = node.getSourceDistance() - } - - /** - * Holds if there is a partial data flow path from `node` to `sink`. The - * approximate distance between `node` and the closest sink is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards source definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sinks is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - * - * Note that reverse flow has slightly lower precision than the corresponding - * forward flow, as reverse flow disregards type pruning among other features. - */ - final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { - revPartialFlow(node, sink, this) and - dist = node.getSinkDistance() - } } /** @@ -263,90 +224,6 @@ abstract private class ConfigurationRecursionPrevention extends Configuration { } } -private newtype TNodeEx = - TNodeNormal(Node n) or - TNodeImplicitRead(Node n, boolean hasRead) { - any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true] - } - -private class NodeEx extends TNodeEx { - string toString() { - result = this.asNode().toString() - or - exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") - } - - Node asNode() { this = TNodeNormal(result) } - - predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } - - Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } - - pragma[nomagic] - private DataFlowCallable getEnclosingCallable0() { - nodeEnclosingCallable(this.projectToNode(), result) - } - - pragma[inline] - DataFlowCallable getEnclosingCallable() { - pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) - } - - pragma[nomagic] - private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } - - pragma[inline] - DataFlowType getDataFlowType() { - pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) - } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -private class ArgNodeEx extends NodeEx { - ArgNodeEx() { this.asNode() instanceof ArgNode } -} - -private class ParamNodeEx extends NodeEx { - ParamNodeEx() { this.asNode() instanceof ParamNode } - - predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { - this.asNode().(ParamNode).isParameterOf(c, pos) - } - - ParameterPosition getPosition() { this.isParameterOf(_, result) } -} - -private class RetNodeEx extends NodeEx { - RetNodeEx() { this.asNode() instanceof ReturnNodeExt } - - ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } - - ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } -} - -private predicate inBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierIn(n) - | - config.isSource(n) or config.isSource(n, _) - ) -} - -private predicate outBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierOut(n) - | - config.isSink(n) or config.isSink(n, _) - ) -} - /** A bridge class to access the deprecated `isBarrierGuard`. */ private class BarrierGuardGuardedNodeBridge extends Unit { abstract predicate guardedNode(Node n, Configuration config); @@ -370,3019 +247,94 @@ private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { } } -pragma[nomagic] -private predicate fullBarrier(NodeEx node, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n) +private FlowState relevantState(Configuration config) { + config.isSource(_, result) or + config.isSink(_, result) or + config.isBarrier(_, result) or + config.isAdditionalFlowStep(_, result, _, _) or + config.isAdditionalFlowStep(_, _, _, result) +} + +private newtype TConfigState = + TMkConfigState(Configuration config, FlowState state) { + state = relevantState(config) or state instanceof FlowStateEmpty + } + +private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) } + +private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) } + +private predicate singleConfiguration() { 1 = strictcount(Configuration c) } + +private module Config implements FullStateConfigSig { + class FlowState = TConfigState; + + predicate isSource(Node source, FlowState state) { + getConfig(state).isSource(source, getState(state)) or - config.isBarrierIn(n) and - not config.isSource(n) and - not config.isSource(n, _) + getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty + } + + predicate isSink(Node sink, FlowState state) { + getConfig(state).isSink(sink, getState(state)) or - config.isBarrierOut(n) and - not config.isSink(n) and - not config.isSink(n, _) + getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty + } + + predicate isBarrier(Node node) { none() } + + predicate isBarrier(Node node, FlowState state) { + getConfig(state).isBarrier(node, getState(state)) or + getConfig(state).isBarrier(node) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state)) + } + + predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) } + + predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) } + + predicate isAdditionalFlowStep(Node node1, Node node2) { + singleConfiguration() and + any(Configuration config).isAdditionalFlowStep(node1, node2) + } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and + getConfig(state2) = getConfig(state1) or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config) - ) -} - -pragma[nomagic] -private predicate stateBarrier(NodeEx node, FlowState state, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n, state) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config) - ) -} - -pragma[nomagic] -private predicate sourceNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSource(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSource(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -pragma[nomagic] -private predicate sinkNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSink(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSink(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -/** Provides the relevant barriers for a step from `node1` to `node2`. */ -pragma[inline] -private predicate stepFilter(NodeEx node1, NodeEx node2, Configuration config) { - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) -} - -/** - * Holds if data can flow in one local step from `node1` to `node2`. - */ -private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.asNode() = n and - node2.isImplicitReadNode(n, false) and - not fullBarrier(node1, config) - ) -} - -/** - * Holds if the additional step from `node1` to `node2` does not jump between callables. - */ -private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.isImplicitReadNode(n, true) and - node2.asNode() = n and - not fullBarrier(node2, config) - ) -} - -private predicate additionalLocalStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) - ) -} - -/** - * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. - */ -private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if the additional step from `node1` to `node2` jumps between callables. - */ -private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate additionalJumpStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -pragma[nomagic] -private predicate readSet(NodeEx node1, ContentSet c, NodeEx node2, Configuration config) { - readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and - stepFilter(node1, node2, config) - or - exists(Node n | - node2.isImplicitReadNode(n, true) and - node1.isImplicitReadNode(n, _) and - config.allowImplicitRead(n, c) - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(ContentSet cs | - readSet(node1, cs, node2, config) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate clearsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - clearsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate expectsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - expectsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -pragma[nomagic] -private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } - -pragma[nomagic] -private predicate hasReadStep(Content c, Configuration config) { read(_, c, _, config) } - -pragma[nomagic] -private predicate store( - NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config -) { - store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), - contentType) and - hasReadStep(tc.getContent(), config) and - stepFilter(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { - viableReturnPosOut(call, pos, out.asNode()) -} - -pragma[nomagic] -private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { - viableParamArg(call, p.asNode(), arg.asNode()) -} - -/** - * Holds if field flow should be used for the given configuration. - */ -private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } - -private predicate hasSourceCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSourceCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate hasSinkCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSinkCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if flow from `p` to a return node of kind `kind` is allowed. - * - * We don't expect a parameter to return stored in itself, unless - * explicitly allowed - */ -bindingset[p, kind] -private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { - exists(ParameterPosition pos | p.isParameterOf(_, pos) | - not kind.(ParamUpdateReturnKind).getPosition() = pos - or - allowParameterReturnInSelfCached(p.asNode()) - ) -} - -private module Stage1 implements StageSig { - class Ap extends int { - // workaround for bad functionality-induced joins (happens when using `Unit`) - pragma[nomagic] - Ap() { this in [0 .. 1] and this < 1 } - } - - private class Cc = boolean; - - /* Begin: Stage 1 logic. */ - /** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `cc` records whether the node is reached through an - * argument in a call. - */ - private predicate fwdFlow(NodeEx node, Cc cc, Configuration config) { - sourceNode(node, _, config) and - if hasSourceCallCtx(config) then cc = true else cc = false - or - exists(NodeEx mid | fwdFlow(mid, cc, config) | - localFlowStep(mid, node, config) or - additionalLocalFlowStep(mid, node, config) or - additionalLocalStateStep(mid, _, node, _, config) - ) - or - exists(NodeEx mid | fwdFlow(mid, _, config) and cc = false | - jumpStep(mid, node, config) or - additionalJumpStep(mid, node, config) or - additionalJumpStateStep(mid, _, node, _, config) - ) - or - // store - exists(NodeEx mid | - useFieldFlow(config) and - fwdFlow(mid, cc, config) and - store(mid, _, node, _, config) - ) - or - // read - exists(ContentSet c | - fwdFlowReadSet(c, node, cc, config) and - fwdFlowConsCandSet(c, _, config) - ) - or - // flow into a callable - fwdFlowIn(_, _, _, node, config) and - cc = true - or - // flow out of a callable - fwdFlowOut(_, node, false, config) and - cc = false - or - // flow through a callable - exists(DataFlowCall call | - fwdFlowOutFromArg(call, node, config) and - fwdFlowIsEntered(call, cc, config) - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowIn( - DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p, Configuration config - ) { - // call context cannot help reduce virtual dispatch - fwdFlow(arg, cc, config) and - viableParamArgEx(call, p, arg) and - not fullBarrier(p, config) and - ( - cc = false - or - cc = true and - not reducedViableImplInCallContext(call, _, _) - ) - or - // call context may help reduce virtual dispatch - exists(DataFlowCallable target | - fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target, config) and - target = viableImplInSomeFwdFlowCallContextExt(call, config) and - cc = true - ) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - fwdFlowIn(call, _, cc, _, config) - } - - pragma[nomagic] - private predicate fwdFlowInReducedViableImplInSomeCallContext( - DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target, Configuration config - ) { - fwdFlow(arg, true, config) and - viableParamArgEx(call, p, arg) and - reducedViableImplInCallContext(call, _, _) and - target = p.getEnclosingCallable() and - not fullBarrier(p, config) - } - - /** - * Gets a viable dispatch target of `call` in the context `ctx`. This is - * restricted to those `call`s for which a context might make a difference, - * and to `ctx`s that are reachable in `fwdFlow`. - */ - pragma[nomagic] - private DataFlowCallable viableImplInSomeFwdFlowCallContextExt( - DataFlowCall call, Configuration config - ) { - exists(DataFlowCall ctx | - fwdFlowIsEntered(ctx, _, config) and - result = viableImplInCallContextExt(call, ctx) - ) - } - - private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) } - - pragma[nomagic] - private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc, Configuration config) { - exists(NodeEx mid | - fwdFlow(mid, cc, config) and - readSet(mid, c, node, config) - ) - } - - /** - * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - fwdFlow(mid, _, config) and - store(mid, tc, node, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `cs` may be interpreted in a read as the target of some store - * into `c`, in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCandSet(ContentSet cs, Content c, Configuration config) { - fwdFlowConsCand(c, config) and - c = cs.getAReadContent() - } - - pragma[nomagic] - private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { - exists(RetNodeEx ret | - fwdFlow(ret, cc, config) and - ret.getReturnPosition() = pos - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) { - exists(ReturnPosition pos | - fwdFlowReturnPosition(pos, cc, config) and - viableReturnPosOutEx(call, pos, out) and - not fullBarrier(out, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) { - fwdFlowOut(call, out, true, config) - } - - private predicate stateStepFwd(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1 | - additionalLocalStateStep(node1, state1, _, state2, config) or - additionalJumpStateStep(node1, state1, _, state2, config) - | - fwdFlow(node1, config) - ) - } - - private predicate fwdFlowState(FlowState state, Configuration config) { - sourceNode(_, state, config) - or - exists(FlowState state0 | - fwdFlowState(state0, config) and - stateStepFwd(state0, state, config) - ) - } - - /** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink. - */ - pragma[nomagic] - private predicate revFlow(NodeEx node, boolean toReturn, Configuration config) { - revFlow0(node, toReturn, config) and - fwdFlow(node, config) - } - - pragma[nomagic] - private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) { - exists(FlowState state | - fwdFlow(node, pragma[only_bind_into](config)) and - sinkNode(node, state, config) and - fwdFlowState(state, pragma[only_bind_into](config)) and - if hasSinkCallCtx(config) then toReturn = true else toReturn = false - ) - or - exists(NodeEx mid | revFlow(mid, toReturn, config) | - localFlowStep(node, mid, config) or - additionalLocalFlowStep(node, mid, config) or - additionalLocalStateStep(node, _, mid, _, config) - ) - or - exists(NodeEx mid | revFlow(mid, _, config) and toReturn = false | - jumpStep(node, mid, config) or - additionalJumpStep(node, mid, config) or - additionalJumpStateStep(node, _, mid, _, config) - ) - or - // store - exists(Content c | - revFlowStore(c, node, toReturn, config) and - revFlowConsCand(c, config) - ) - or - // read - exists(NodeEx mid, ContentSet c | - readSet(node, c, mid, config) and - fwdFlowConsCandSet(c, _, pragma[only_bind_into](config)) and - revFlow(mid, toReturn, pragma[only_bind_into](config)) - ) - or - // flow into a callable - revFlowIn(_, node, false, config) and - toReturn = false - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(pos, config) and - node.(RetNodeEx).getReturnPosition() = pos and - toReturn = true - ) - or - // flow through a callable - exists(DataFlowCall call | - revFlowInToReturn(call, node, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - /** - * Holds if `c` is the target of a read in the flow covered by `revFlow`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, ContentSet cs | - fwdFlow(node, pragma[only_bind_into](config)) and - readSet(node, cs, mid, config) and - fwdFlowConsCandSet(cs, c, pragma[only_bind_into](config)) and - revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) { - exists(NodeEx mid, TypedContent tc | - revFlow(mid, toReturn, pragma[only_bind_into](config)) and - fwdFlowConsCand(c, pragma[only_bind_into](config)) and - store(node, tc, mid, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `c` is the target of both a read and a store in the flow covered - * by `revFlow`. - */ - pragma[nomagic] - additional predicate revFlowIsReadAndStored(Content c, Configuration conf) { - revFlowConsCand(c, conf) and - revFlowStore(c, _, _, conf) - } - - pragma[nomagic] - additional predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config - ) { - fwdFlowReturnPosition(pos, _, config) and - viableReturnPosOutEx(call, pos, out) - } - - pragma[nomagic] - private predicate revFlowOut(ReturnPosition pos, Configuration config) { - exists(NodeEx out | - revFlow(out, _, config) and - viableReturnPosOutNodeCandFwd1(_, pos, out, config) - ) - } - - pragma[nomagic] - additional predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config - ) { - fwdFlowIn(call, arg, _, p, config) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate revFlowIn( - DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config - ) { - exists(ParamNodeEx p | - revFlow(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) { - revFlowIn(call, arg, true, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(NodeEx out | - revFlow(out, toReturn, config) and - fwdFlowOutFromArg(call, out, config) - ) - } - - private predicate stateStepRev(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - additionalLocalStateStep(node1, state1, node2, state2, config) or - additionalJumpStateStep(node1, state1, node2, state2, config) - | - revFlow(node1, _, pragma[only_bind_into](config)) and - revFlow(node2, _, pragma[only_bind_into](config)) and - fwdFlowState(state1, pragma[only_bind_into](config)) and - fwdFlowState(state2, pragma[only_bind_into](config)) - ) - } - - additional predicate revFlowState(FlowState state, Configuration config) { - exists(NodeEx node | - sinkNode(node, state, config) and - revFlow(node, _, pragma[only_bind_into](config)) and - fwdFlowState(state, pragma[only_bind_into](config)) - ) - or - exists(FlowState state0 | - revFlowState(state0, config) and - stateStepRev(state, state0, config) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Content c | - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - revFlow(node2, pragma[only_bind_into](config)) and - store(node1, tc, node2, contentType, config) and - c = tc.getContent() and - exists(ap1) - ) - } - - pragma[nomagic] - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) { - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - read(n1, c, n2, pragma[only_bind_into](config)) and - revFlow(n2, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, config) and - exists(ap) - } - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, _, pragma[only_bind_into](config)) and - exists(state) and - exists(ap) - } - - private predicate throughFlowNodeCand(NodeEx node, Configuration config) { - revFlow(node, true, config) and - fwdFlow(node, true, config) and - not inBarrier(node, config) and - not outBarrier(node, config) - } - - /** Holds if flow may return from `callable`. */ - pragma[nomagic] - private predicate returnFlowCallableNodeCand( - DataFlowCallable callable, ReturnKindExt kind, Configuration config - ) { - exists(RetNodeEx ret | - throughFlowNodeCand(ret, config) and - callable = ret.getEnclosingCallable() and - kind = ret.getKind() - ) - } - - /** - * Holds if flow may enter through `p` and reach a return node making `p` a - * candidate for the origin of a summary. - */ - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(DataFlowCallable c, ReturnKindExt kind | - throughFlowNodeCand(p, config) and - returnFlowCallableNodeCand(c, kind, config) and - p.getEnclosingCallable() = c and - exists(ap) and - parameterFlowThroughAllowed(p, kind) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - throughFlowNodeCand(ret, config) and - kind = ret.getKind() and - exists(argAp) and - exists(ap) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, boolean toReturn | - revFlow(arg, toReturn, config) and - revFlowInToReturn(call, arg, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, config)) and - fields = count(Content f0 | fwdFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | fwdFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, config)) and - fields = count(Content f0 | revFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | revFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | revFlow(n, b, config)) - } - /* End: Stage 1 logic. */ -} - -pragma[noinline] -private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - localFlowStep(node1, node2, config) -} - -pragma[noinline] -private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - additionalLocalFlowStep(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCand1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config -) { - Stage1::revFlow(out, config) and - Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, Configuration config -) { - exists(ReturnPosition pos | - viableReturnPosOutNodeCand1(call, pos, out, config) and - pos = ret.getReturnPosition() and - kind = pos.getKind() and - Stage1::revFlow(ret, config) and - not outBarrier(ret, config) and - not inBarrier(out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCand1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config -) { - Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and - Stage1::revFlow(arg, config) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config -) { - viableParamArgNodeCand1(call, p, arg, config) and - Stage1::revFlow(p, config) and - not outBarrier(arg, config) and - not inBarrier(p, config) -} - -/** - * Gets the amount of forward branching on the origin of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int branch(NodeEx n1, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n1, _, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) - ) -} - -/** - * Gets the amount of backward branching on the target of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int join(NodeEx n2, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n, _, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) - ) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. The - * `allowsFieldFlow` flag indicates whether the branching is within the limit - * specified by the configuration. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, ret, kind, out, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(ret, pragma[only_bind_into](config)) and - j = join(out, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. The `allowsFieldFlow` flag indicates whether - * the branching is within the limit specified by the configuration. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config -) { - flowIntoCallNodeCand1(call, arg, p, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(arg, pragma[only_bind_into](config)) and - j = join(p, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -private signature module StageSig { - class Ap; - - predicate revFlow(NodeEx node, Configuration config); - - predicate revFlowAp(NodeEx node, Ap ap, Configuration config); - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config); - - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config); - - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config); - - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ); - - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ); - - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config); -} - -private module MkStage { - class ApApprox = PrevStage::Ap; - - signature module StageParam { - class Ap; - - class ApNil extends Ap; - - bindingset[result, ap] - ApApprox getApprox(Ap ap); - - ApNil getApNil(NodeEx node); - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail); - - /** - * An approximation of `Content` that corresponds to the precision level of - * `Ap`, such that the mappings from both `Ap` and `Content` to this type - * are functional. - */ - class ApHeadContent; - - ApHeadContent getHeadContent(Ap ap); - - ApHeadContent projectToHeadContent(Content c); - - class ApOption; - - ApOption apNone(); - - ApOption apSome(Ap ap); - - class Cc; - - class CcCall extends Cc; - - // TODO: member predicate on CcCall - predicate matchesCall(CcCall cc, DataFlowCall call); - - class CcNoCall extends Cc; - - Cc ccNone(); - - CcCall ccSomeCall(); - - class LocalCc; - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc); - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ); - - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config - ); - - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config - ); - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config); - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType); - } - - module Stage implements StageSig { - import Param - - /* Begin: Stage logic. */ - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - private predicate revFlowApAlias(NodeEx node, ApApprox apa, Configuration config) { - PrevStage::revFlowAp(node, apa, config) - } - - pragma[nomagic] - private predicate flowIntoCallApa( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa, - Configuration config - ) { - flowIntoCall(call, arg, p, allowsFieldFlow, config) and - PrevStage::revFlowAp(p, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(arg, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowOutOfCallApa( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - ApApprox apa, Configuration config - ) { - flowOutOfCall(call, ret, kind, out, allowsFieldFlow, config) and - PrevStage::revFlowAp(out, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(ret, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowThroughOutOfCall( - DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, - ApApprox argApa, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa, pragma[only_bind_into](config)) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) and - matchesCall(ccc, call) - ) - } - - /** - * Holds if `node` is reachable with access path `ap` from a source in the - * configuration `config`. - * - * The call context `cc` records whether the node is reached through an - * argument in a call, and if so, `summaryCtx` and `argAp` record the - * corresponding parameter position and access path of that argument, respectively. - */ - pragma[nomagic] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa, config) and - PrevStage::revFlow(node, state, apa, config) and - filter(node, state, ap, config) - } - - pragma[inline] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, cc, summaryCtx, argAp, ap, _, config) - } - - pragma[nomagic] - private predicate fwdFlow0( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - sourceNode(node, state, config) and - (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and - argAp = apNone() and - summaryCtx = TParamNodeNone() and - ap = getApNil(node) and - apa = getApprox(ap) - or - exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | - fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0, config) and - localCc = getLocalCc(mid, cc) - | - localStep(mid, state0, node, state, true, _, config, localCc) and - ap = ap0 and - apa = apa0 - or - localStep(mid, state0, node, state, false, ap, config, localCc) and - ap0 instanceof ApNil and - apa = getApprox(ap) - ) - or - exists(NodeEx mid | - fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa, pragma[only_bind_into](config)) and - jumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(mid, state, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(mid, state0, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStateStep(mid, state0, node, state, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - // store - exists(TypedContent tc, Ap ap0 | - fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp, config) and - ap = apCons(tc, ap0) and - apa = getApprox(ap) - ) - or - // read - exists(Ap ap0, Content c | - fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp, config) and - fwdFlowConsCand(ap0, c, ap, config) and - apa = getApprox(ap) - ) - or - // flow into a callable - fwdFlowIn(_, node, state, _, cc, _, _, ap, apa, config) and - if PrevStage::parameterMayFlowThrough(node, apa, config) - then ( - summaryCtx = TParamNodeSome(node.asNode()) and - argAp = apSome(ap) - ) else ( - summaryCtx = TParamNodeNone() and argAp = apNone() - ) - or - // flow out of a callable - exists( - DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, - DataFlowCallable inner - | - fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa, config) and - flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa, config) and - inner = ret.getEnclosingCallable() and - cc = getCallContextReturn(inner, call, innercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - or - // flow through a callable - exists( - DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, ApApprox innerArgApa - | - fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowStore( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(DataFlowType contentType, ApApprox apa1 | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1, config) and - PrevStage::storeStepCand(node1, apa1, tc, node2, contentType, config) and - typecheckStore(ap1, contentType) - ) - } - - /** - * Holds if forward flow with access path `tail` reaches a store of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(TypedContent tc | - fwdFlowStore(_, tail, tc, _, _, _, _, _, config) and - tc.getContent() = c and - cons = apCons(tc, tail) - ) - } - - pragma[nomagic] - private predicate readStepCand( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - PrevStage::readStepCand(node1, c, node2, config) and - apc = projectToHeadContent(c) - } - - bindingset[node1, apc] - pragma[inline_late] - private predicate readStepCand0( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - readStepCand(node1, apc, c, node2, config) - } - - pragma[nomagic] - private predicate fwdFlowRead( - Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(ApHeadContent apc | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap, config) and - apc = getHeadContent(ap) and - readStepCand0(node1, apc, c, node2, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowIn( - DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, - ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa, Configuration config - ) { - exists(ArgNodeEx arg, boolean allowsFieldFlow | - fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa, config) and - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowRetFromArg( - RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, ApApprox argApa, - Ap ap, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - fwdFlow(pragma[only_bind_into](ret), state, ccc, - TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), - pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa), - pragma[only_bind_into](config)) and - kind = ret.getKind() and - parameterFlowThroughAllowed(summaryCtx, kind) and - argApa = getApprox(argAp) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) - ) - } - - pragma[inline] - private predicate fwdFlowThrough0( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, - Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa, config) and - fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp, config) - } - - pragma[nomagic] - private predicate fwdFlowThrough( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa, - config) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` - * and data might flow through the target callable and back out at `call`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered( - DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, - ParamNodeEx p, Ap ap, Configuration config - ) { - exists(ApApprox apa | - fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(p, apa, config) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate storeStepFwd( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config - ) { - fwdFlowStore(node1, ap1, tc, node2, _, _, _, _, config) and - ap2 = apCons(tc, ap1) and - fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _, config) - } - - private predicate readStepFwd( - NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config - ) { - fwdFlowRead(ap1, c, n1, n2, _, _, _, _, config) and - fwdFlowConsCand(ap1, c, ap2, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough0( - DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, - ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, - innerArgApa, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough( - RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, - Ap ap, Configuration config - ) { - exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | - returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa, config) and - pos = ret.getReturnPosition() and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowThroughIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap, - Configuration config - ) { - exists(ApApprox argApa | - flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), - allowsFieldFlow, argApa, pragma[only_bind_into](config)) and - fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa, - pragma[only_bind_into](config)) and - returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap, - pragma[only_bind_into](config)) and - if allowsFieldFlow = false then argAp instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowIntoCallAp( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap, - Configuration config - ) { - exists(ApApprox apa | - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - fwdFlow(arg, _, _, _, _, ap, apa, config) - ) - } - - pragma[nomagic] - private predicate flowOutOfCallAp( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, - Ap ap, Configuration config - ) { - exists(ApApprox apa | - flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa, config) and - fwdFlow(ret, _, _, _, _, ap, apa, config) and - pos = ret.getReturnPosition() - ) - } - - /** - * Holds if `node` with access path `ap` is part of a path from a source to a - * sink in the configuration `config`. - * - * The parameter `returnCtx` records whether (and how) the node must be returned - * from the enclosing callable in order to reach a sink, and if so, `returnAp` - * records the access path of the returned value. - */ - pragma[nomagic] - additional predicate revFlow( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - revFlow0(node, state, returnCtx, returnAp, ap, config) and - fwdFlow(node, state, _, _, _, ap, config) - } - - pragma[nomagic] - private predicate revFlow0( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, _, _, _, ap, config) and - sinkNode(node, state, config) and - ( - if hasSinkCallCtx(config) - then returnCtx = TReturnCtxNoFlowThrough() - else returnCtx = TReturnCtxNone() - ) and - returnAp = apNone() and - ap instanceof ApNil - or - exists(NodeEx mid, FlowState state0 | - localStep(node, state, mid, state0, true, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, ap, config) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap, pragma[only_bind_into](config)) and - localStep(node, pragma[only_bind_into](state), mid, state0, false, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, nil, pragma[only_bind_into](config)) and - ap instanceof ApNil - ) - or - exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, state, _, _, ap, config) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStep(node, mid, config) and - revFlow(pragma[only_bind_into](mid), state, _, _, nil, pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStateStep(node, state, mid, state0, config) and - revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil, - pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - // store - exists(Ap ap0, Content c | - revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp, config) and - revFlowConsCand(ap0, c, ap, config) - ) - or - // read - exists(NodeEx mid, Ap ap0 | - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - readStepFwd(node, ap, _, mid, ap0, config) - ) - or - // flow into a callable - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, state, TReturnCtxNone(), returnAp, ap, config) and - flowIntoCallAp(_, node, p, allowsFieldFlow, ap, config) and - (if allowsFieldFlow = false then ap instanceof ApNil else any()) and - returnCtx = TReturnCtxNone() - ) - or - // flow through a callable - exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, node, p, _, ap, innerReturnAp, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(_, node, pos, state, _, _, ap, config) and - if returnFlowsThrough(node, pos, state, _, _, _, ap, config) - then ( - returnCtx = TReturnCtxMaybeFlowThrough(pos) and - returnAp = apSome(ap) - ) else ( - returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() - ) - ) - } - - pragma[nomagic] - private predicate revFlowStore( - Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, - ReturnCtx returnCtx, ApOption returnAp, Configuration config - ) { - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - storeStepFwd(node, ap, tc, mid, ap0, config) and - tc.getContent() = c - } - - /** - * Holds if reverse flow with access path `tail` reaches a read of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(NodeEx mid, Ap tail0 | - revFlow(mid, _, _, _, tail, config) and - tail = pragma[only_bind_into](tail0) and - readStepFwd(_, cons, c, mid, tail0, config) - ) - } - - pragma[nomagic] - private predicate revFlowOut( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, - ApOption returnAp, Ap ap, Configuration config - ) { - exists(NodeEx out, boolean allowsFieldFlow | - revFlow(out, state, returnCtx, returnAp, ap, config) and - flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate revFlowParamToReturn( - ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap, Configuration config - ) { - revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), - pragma[only_bind_into](ap), pragma[only_bind_into](config)) and - parameterFlowThroughAllowed(p, pos.getKind()) and - PrevStage::parameterMayFlowThrough(p, getApprox(ap), config) - } - - pragma[nomagic] - private predicate revFlowThrough( - DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, - ApOption returnAp, Ap ap, Ap innerReturnAp, Configuration config - ) { - revFlowParamToReturn(p, state, pos, innerReturnAp, ap, config) and - revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned( - DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap, - Configuration config - ) { - exists(RetNodeEx ret, FlowState state, CcCall ccc | - revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap, config) and - returnFlowsThrough(ret, pos, state, ccc, _, _, ap, config) and - matchesCall(ccc, call) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Ap ap2, Content c | - PrevStage::storeStepCand(node1, _, tc, node2, contentType, config) and - revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _, config) and - revFlowConsCand(ap2, c, ap1, config) - ) - } - - predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(Ap ap1, Ap ap2 | - revFlow(node2, _, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and - readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _, - pragma[only_bind_into](config)) - ) - } - - additional predicate revFlow(NodeEx node, FlowState state, Configuration config) { - revFlow(node, state, _, _, _, config) - } - - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, _, _, ap, config) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, _, _, _, ap, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, Configuration config) { - revFlow(node, _, _, _, _, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, ap, config) - } - - private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepFwd(_, ap, tc, _, _, config) - } - - private predicate revConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepCand(_, ap, tc, _, _, config) - } - - private predicate validAp(Ap ap, Configuration config) { - revFlow(_, _, _, _, ap, config) and ap instanceof ApNil - or - exists(TypedContent head, Ap tail | - consCand(head, tail, config) and - ap = apCons(head, tail) - ) - } - - additional predicate consCand(TypedContent tc, Ap ap, Configuration config) { - revConsCand(tc, ap, config) and - validAp(ap, config) - } - - pragma[nomagic] - private predicate parameterFlowsThroughRev( - ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp, Configuration config - ) { - revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap, config) and - parameterFlowThroughAllowed(p, pos.getKind()) - } - - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(ReturnPosition pos | - returnFlowsThrough(_, pos, _, _, p, ap, _, config) and - parameterFlowsThroughRev(p, ap, pos, _, config) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - exists(ParamNodeEx p, ReturnPosition pos | - returnFlowsThrough(ret, pos, _, _, p, argAp, ap, config) and - parameterFlowsThroughRev(p, argAp, pos, ap, config) and - kind = pos.getKind() - ) - } - - pragma[nomagic] - private predicate revFlowThroughArg( - DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, - Ap ap, Configuration config - ) { - exists(ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp, config) - ) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | - revFlow(arg, state, returnCtx, returnAp, ap, config) and - revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, config)) and - fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and - states = count(FlowState state | fwdFlow(_, state, _, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | - fwdFlow(n, state, cc, summaryCtx, argAp, ap, config) - ) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, _, _, _, config)) and - fields = count(TypedContent f0 | consCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and - states = count(FlowState state | revFlow(_, state, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | - revFlow(n, state, returnCtx, retAp, ap, config) - ) - } - /* End: Stage logic. */ - } -} - -private module BooleanCallContext { - class Cc extends boolean { - Cc() { this in [true, false] } - } - - class CcCall extends Cc { - CcCall() { this = true } - } - - /** Holds if the call context may be `call`. */ - predicate matchesCall(CcCall cc, DataFlowCall call) { any() } - - class CcNoCall extends Cc { - CcNoCall() { this = false } - } - - Cc ccNone() { result = false } - - CcCall ccSomeCall() { result = true } - - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } -} - -private module Level1CallContext { - class Cc = CallContext; - - class CcCall = CallContextCall; - - pragma[inline] - predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } - - class CcNoCall = CallContextNoCall; - - Cc ccNone() { result instanceof CallContextAny } - - CcCall ccSomeCall() { result instanceof CallContextSomeCall } - - module NoLocalCallContext { - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSiteDispatch(call, c) - then result = TSpecificCall(call) - else result = TSomeCall() - } - } - - module LocalCallContext { - class LocalCc = LocalCallContext; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { - result = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - node.getEnclosingCallable()) - } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() - } - } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { - checkCallContextReturn(innercc, c, call) and - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() - } -} - -private module Stage2Param implements MkStage::StageParam { - private module PrevStage = Stage1; - - class Ap extends boolean { - Ap() { this in [true, false] } - } - - class ApNil extends Ap { - ApNil() { this = false } - } - - bindingset[result, ap] - PrevStage::Ap getApprox(Ap ap) { any() } - - ApNil getApNil(NodeEx node) { Stage1::revFlow(node, _) and exists(result) } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } - - class ApHeadContent = Unit; - - pragma[inline] - ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } - - ApHeadContent projectToHeadContent(Content c) { any() } - - class ApOption = BooleanOption; - - ApOption apNone() { result = TBooleanNone() } - - ApOption apSome(Ap ap) { result = TBooleanSome(ap) } - - import Level1CallContext - import NoLocalCallContext - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - ( - preservesValue = true and - localFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalStateStep(node1, state1, node2, state2, config) - ) and - exists(ap) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand1/6; - - predicate flowIntoCall = flowIntoCallNodeCand1/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - expectsContentEx(node, c) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - PrevStage::revFlowState(state, pragma[only_bind_into](config)) and - exists(ap) and - not stateBarrier(node, state, config) and - ( - notExpectsContent(node) - or - ap = true and - expectsContentCand(node, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage2 implements StageSig { - import MkStage::Stage -} - -pragma[nomagic] -private predicate flowOutOfCallNodeCand2( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -pragma[nomagic] -private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -private module LocalFlowBigStep { - /** - * A node where some checking is required, and hence the big-step relation - * is not allowed to step over. - */ - private class FlowCheckNode extends NodeEx { - FlowCheckNode() { - castNode(this.asNode()) or - clearsContentCached(this.asNode(), _) or - expectsContentCached(this.asNode(), _) - } - } - - /** - * Holds if `node` can be the first node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowEntry(NodeEx node, FlowState state, Configuration config) { - Stage2::revFlow(node, state, config) and - ( - sourceNode(node, state, config) - or - jumpStep(_, node, config) - or - additionalJumpStep(_, node, config) - or - additionalJumpStateStep(_, _, node, state, config) - or - node instanceof ParamNodeEx - or - node.asNode() instanceof OutNodeExt - or - Stage2::storeStepCand(_, _, _, node, _, config) - or - Stage2::readStepCand(_, _, node, config) - or - node instanceof FlowCheckNode - or - exists(FlowState s | - additionalLocalStateStep(_, s, node, state, config) and - s != state - ) - ) - } - - /** - * Holds if `node` can be the last node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowExit(NodeEx node, FlowState state, Configuration config) { - exists(NodeEx next | Stage2::revFlow(next, state, config) | - jumpStep(node, next, config) or - additionalJumpStep(node, next, config) or - flowIntoCallNodeCand2(_, node, next, _, config) or - flowOutOfCallNodeCand2(_, node, _, next, _, config) or - Stage2::storeStepCand(node, _, _, next, _, config) or - Stage2::readStepCand(node, _, next, config) - ) - or - exists(NodeEx next, FlowState s | Stage2::revFlow(next, s, config) | - additionalJumpStateStep(node, state, next, s, config) - or - additionalLocalStateStep(node, state, next, s, config) and - s != state - ) - or - Stage2::revFlow(node, state, config) and - node instanceof FlowCheckNode - or - sinkNode(node, state, config) - } - - pragma[noinline] - private predicate additionalLocalFlowStepNodeCand2( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, Configuration config - ) { - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 and - Stage2::revFlow(node1, pragma[only_bind_into](state1), false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false, - pragma[only_bind_into](config)) - or - additionalLocalStateStep(node1, state1, node2, state2, config) and - Stage2::revFlow(node1, state1, false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, state2, false, pragma[only_bind_into](config)) - } - - /** - * Holds if the local path from `node1` to `node2` is a prefix of a maximal - * subsequence of local flow steps in a dataflow path. - * - * This is the transitive closure of `[additional]localFlowStep` beginning - * at `localFlowEntry`. - */ - pragma[nomagic] - private predicate localFlowStepPlus( - NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, - Configuration config, LocalCallContext cc - ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and - ( - localFlowEntry(node1, pragma[only_bind_into](state), pragma[only_bind_into](config)) and - ( - localFlowStepNodeCand1(node1, node2, config) and - preservesValue = true and - t = node1.getDataFlowType() and // irrelevant dummy value - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - or - additionalLocalFlowStepNodeCand2(node1, state, node2, state, config) and - preservesValue = false and - t = node2.getDataFlowType() - ) and - node1 != node2 and - cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) - or - exists(NodeEx mid | - localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, - pragma[only_bind_into](config), cc) and - localFlowStepNodeCand1(mid, node2, config) and - not mid instanceof FlowCheckNode and - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - ) - or - exists(NodeEx mid | - localFlowStepPlus(node1, state, mid, _, _, pragma[only_bind_into](config), cc) and - additionalLocalFlowStepNodeCand2(mid, state, node2, state, config) and - not mid instanceof FlowCheckNode and - preservesValue = false and - t = node2.getDataFlowType() - ) - ) - } - - /** - * Holds if `node1` can step to `node2` in one or more local steps and this - * path can occur as a maximal subsequence of local steps in a dataflow path. - */ - pragma[nomagic] - predicate localFlowBigStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - DataFlowType t, Configuration config, LocalCallContext callContext - ) { - localFlowStepPlus(node1, state1, node2, preservesValue, t, config, callContext) and - localFlowExit(node2, state1, config) and - state1 = state2 - or - additionalLocalFlowStepNodeCand2(node1, state1, node2, state2, config) and - state1 != state2 and - preservesValue = false and - t = node2.getDataFlowType() and - callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) - } -} - -private import LocalFlowBigStep - -private module Stage3Param implements MkStage::StageParam { - private module PrevStage = Stage2; - - class Ap = ApproxAccessPathFront; - - class ApNil = ApproxAccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } - - class ApHeadContent = ContentApprox; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - predicate projectToHeadContent = getContentApprox/1; - - class ApOption = ApproxAccessPathFrontOption; - - ApOption apNone() { result = TApproxAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } - - import BooleanCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApproxAccessPathFrontNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand2/6; - - predicate flowIntoCall = flowIntoCallNodeCand2/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getAHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage3 implements StageSig { - import MkStage::Stage -} - -private module Stage4Param implements MkStage::StageParam { - private module PrevStage = Stage3; - - class Ap = AccessPathFront; - - class ApNil = AccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathFrontOption; - - ApOption apNone() { result = TAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } - - import BooleanCallContext - - pragma[nomagic] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) and - exists(lcc) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clearSet(NodeEx node, ContentSet c, Configuration config) { - PrevStage::revFlow(node, config) and - clearsContentCached(node.asNode(), c) - } - - pragma[nomagic] - private predicate clearContent(NodeEx node, Content c, Configuration config) { - exists(ContentSet cs | - PrevStage::readStepCand(_, pragma[only_bind_into](c), _, pragma[only_bind_into](config)) and - c = cs.getAReadContent() and - clearSet(node, cs, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clear(NodeEx node, Ap ap, Configuration config) { - clearContent(node, ap.getHead().getContent(), config) - } - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - not clear(node, ap, config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage4 implements StageSig { - import MkStage::Stage -} - -/** - * Holds if `argApf` is recorded as the summary context for flow reaching `node` - * and remains relevant for the following pruning stage. - */ -private predicate flowCandSummaryCtx( - NodeEx node, FlowState state, AccessPathFront argApf, Configuration config -) { - exists(AccessPathFront apf | - Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf, config) and - Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf, - config) - ) -} - -/** - * Holds if a length 2 access path approximation with the head `tc` is expected - * to be expensive. - */ -private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { - exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf, config)) and - nodes = - strictcount(NodeEx n, FlowState state | - Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - or - flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - ) and - accessPathApproxCostLimits(apLimit, tupleLimit) and - apLimit < tails and - tupleLimit < (tails - 1) * nodes and - not tc.forceHighPrecision() - ) -} - -private newtype TAccessPathApprox = - TNil(DataFlowType t) or - TConsNil(TypedContent tc, DataFlowType t) { - Stage4::consCand(tc, TFrontNil(t), _) and - not expensiveLen2unfolding(tc, _) - } or - TConsCons(TypedContent tc1, TypedContent tc2, int len) { - Stage4::consCand(tc1, TFrontHead(tc2), _) and - len in [2 .. accessPathLimit()] and - not expensiveLen2unfolding(tc1, _) - } or - TCons1(TypedContent tc, int len) { - len in [1 .. accessPathLimit()] and - expensiveLen2unfolding(tc, _) - } - -/** - * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only - * the first two elements of the list and its length are tracked. If data flows - * from a source to a given node with a given `AccessPathApprox`, this indicates - * the sequence of dereference operations needed to get from the value in the node - * to the tracked object. The final type indicates the type of the tracked object. - */ -abstract private class AccessPathApprox extends TAccessPathApprox { - abstract string toString(); - - abstract TypedContent getHead(); - - abstract int len(); - - abstract DataFlowType getType(); - - abstract AccessPathFront getFront(); - - /** Gets the access path obtained by popping `head` from this path, if any. */ - abstract AccessPathApprox pop(TypedContent head); -} - -private class AccessPathApproxNil extends AccessPathApprox, TNil { - private DataFlowType t; - - AccessPathApproxNil() { this = TNil(t) } - - override string toString() { result = concat(": " + ppReprType(t)) } - - override TypedContent getHead() { none() } - - override int len() { result = 0 } - - override DataFlowType getType() { result = t } - - override AccessPathFront getFront() { result = TFrontNil(t) } - - override AccessPathApprox pop(TypedContent head) { none() } -} - -abstract private class AccessPathApproxCons extends AccessPathApprox { } - -private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { - private TypedContent tc; - private DataFlowType t; - - AccessPathApproxConsNil() { this = TConsNil(tc, t) } - - override string toString() { - // The `concat` becomes "" if `ppReprType` has no result. - result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) - } - - override TypedContent getHead() { result = tc } - - override int len() { result = 1 } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } -} - -private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { - private TypedContent tc1; - private TypedContent tc2; - private int len; - - AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - - override string toString() { - if len = 2 - then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" - else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc1 } - - override int len() { result = len } - - override DataFlowType getType() { result = tc1.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc1) } - - override AccessPathApprox pop(TypedContent head) { - head = tc1 and - ( - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - } -} - -private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { - private TypedContent tc; - private int len; - - AccessPathApproxCons1() { this = TCons1(tc, len) } - - override string toString() { - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc } - - override int len() { result = len } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { - head = tc and - ( - exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2), _) | - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - or - exists(DataFlowType t | - len = 1 and - Stage4::consCand(tc, TFrontNil(t), _) and - result = TNil(t) - ) - ) - } -} - -/** Gets the access path obtained by popping `tc` from `ap`, if any. */ -private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } - -/** Gets the access path obtained by pushing `tc` onto `ap`. */ -private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } - -private newtype TAccessPathApproxOption = - TAccessPathApproxNone() or - TAccessPathApproxSome(AccessPathApprox apa) - -private class AccessPathApproxOption extends TAccessPathApproxOption { - string toString() { - this = TAccessPathApproxNone() and result = "" - or - this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) - } -} - -private module Stage5Param implements MkStage::StageParam { - private module PrevStage = Stage4; - - class Ap = AccessPathApprox; - - class ApNil = AccessPathApproxNil; - - pragma[nomagic] - PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathApproxOption; - - ApOption apNone() { result = TAccessPathApproxNone() } - - ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } - - import Level1CallContext - import LocalCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, lcc) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { any() } - - // Type checking is not necessary here as it has already been done in stage 3. - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage5 = MkStage::Stage; - -bindingset[conf, result] -private Configuration unbindConf(Configuration conf) { - exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c)) -} - -pragma[nomagic] -private predicate nodeMayUseSummary0( - NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(AccessPathApprox apa0 | - Stage5::parameterMayFlowThrough(p, _, _) and - Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0, config) and - Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), - TAccessPathApproxSome(apa), apa0, config) - ) -} - -pragma[nomagic] -private predicate nodeMayUseSummary( - NodeEx n, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::parameterMayFlowThrough(p, apa, config) and - nodeMayUseSummary0(n, p, state, apa, config) - ) -} - -private newtype TSummaryCtx = - TSummaryCtxNone() or - TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { - exists(Configuration config | - Stage5::parameterMayFlowThrough(p, ap.getApprox(), config) and - Stage5::revFlow(p, state, _, config) - ) - } - -/** - * A context for generating flow summaries. This represents flow entry through - * a specific parameter with an access path of a specific shape. - * - * Summaries are only created for parameters that may flow through. - */ -abstract private class SummaryCtx extends TSummaryCtx { - abstract string toString(); -} - -/** A summary context from which no flow summary can be generated. */ -private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { - override string toString() { result = "" } -} - -/** A summary context from which a flow summary can be generated. */ -private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParamNodeEx p; - private FlowState s; - private AccessPath ap; - - SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } - - ParameterPosition getParameterPos() { p.isParameterOf(_, result) } - - ParamNodeEx getParamNode() { result = p } - - override string toString() { result = p + ": " + ap } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** - * Gets the number of length 2 access path approximations that correspond to `apa`. - */ -private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { - exists(TypedContent tc, int len | - tc = apa.getHead() and - len = apa.len() and - result = - strictcount(AccessPathFront apf | - Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), - config) - ) - ) -} - -private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = - strictcount(NodeEx n, FlowState state | - Stage5::revFlow(n, state, apa, config) or nodeMayUseSummary(n, state, apa, config) - ) -} - -/** - * Holds if a length 2 access path approximation matching `apa` is expected - * to be expensive. - */ -private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = count1to2unfold(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - apLimit < aps and - tupleLimit < (aps - 1) * nodes - ) -} - -private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { - exists(TypedContent head | - apa.pop(head) = result and - Stage5::consCand(head, result, config) - ) -} - -/** - * Holds with `unfold = false` if a precise head-tail representation of `apa` is - * expected to be expensive. Holds with `unfold = true` otherwise. - */ -private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { - if apa.getHead().forceHighPrecision() - then unfold = true - else - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = countPotentialAps(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true - ) -} - -/** - * Gets the number of `AccessPath`s that correspond to `apa`. - */ -pragma[assume_small_delta] -private int countAps(AccessPathApprox apa, Configuration config) { - evalUnfold(apa, false, config) and - result = 1 and - (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) - or - evalUnfold(apa, false, config) and - result = count1to2unfold(apa, config) and - not expensiveLen1to2unfolding(apa, config) - or - evalUnfold(apa, true, config) and - result = countPotentialAps(apa, config) -} - -/** - * Gets the number of `AccessPath`s that would correspond to `apa` assuming - * that it is expanded to a precise head-tail representation. - */ -language[monotonicAggregates] -pragma[assume_small_delta] -private int countPotentialAps(AccessPathApprox apa, Configuration config) { - apa instanceof AccessPathApproxNil and result = 1 - or - result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) -} - -private newtype TAccessPath = - TAccessPathNil(DataFlowType t) or - TAccessPathCons(TypedContent head, AccessPath tail) { - exists(AccessPathApproxCons apa | - not evalUnfold(apa, false, _) and - head = apa.getHead() and - tail.getApprox() = getATail(apa, _) - ) - } or - TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - not expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head1 = apa.getHead() and - head2 = getATail(apa, _).getHead() - ) - } or - TAccessPathCons1(TypedContent head, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head = apa.getHead() - ) - } - -private newtype TPathNode = - pragma[assume_small_delta] - TPathNodeMid( - NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config - ) { - // A PathNode is introduced by a source ... - Stage5::revFlow(node, state, config) and - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - or - // ... or a step from an existing PathNode to another node. - exists(PathNodeMid mid | - pathStep(mid, node, state, cc, sc, ap) and - pragma[only_bind_into](config) = mid.getConfiguration() and - Stage5::revFlow(node, state, ap.getApprox(), pragma[only_bind_into](config)) - ) - } or - TPathNodeSink(NodeEx node, FlowState state, Configuration config) { - exists(PathNodeMid sink | - sink.isAtSink() and - node = sink.getNodeEx() and - state = sink.getState() and - config = sink.getConfiguration() - ) - } or - TPathNodeSourceGroup(string sourceGroup, Configuration config) { - exists(PathNodeImpl source | - sourceGroup = source.getSourceGroup() and - config = source.getConfiguration() - ) - } or - TPathNodeSinkGroup(string sinkGroup, Configuration config) { - exists(PathNodeSink sink | - sinkGroup = sink.getSinkGroup() and - config = sink.getConfiguration() - ) - } - -/** - * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a - * source to a given node with a given `AccessPath`, this indicates the sequence - * of dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ -private class AccessPath extends TAccessPath { - /** Gets the head of this access path, if any. */ - abstract TypedContent getHead(); - - /** Gets the tail of this access path, if any. */ - abstract AccessPath getTail(); - - /** Gets the front of this access path. */ - abstract AccessPathFront getFront(); - - /** Gets the approximation of this access path. */ - abstract AccessPathApprox getApprox(); - - /** Gets the length of this access path. */ - abstract int length(); - - /** Gets a textual representation of this access path. */ - abstract string toString(); - - /** Gets the access path obtained by popping `tc` from this access path, if any. */ - final AccessPath pop(TypedContent tc) { - result = this.getTail() and - tc = this.getHead() - } - - /** Gets the access path obtained by pushing `tc` onto this access path. */ - final AccessPath push(TypedContent tc) { this = result.pop(tc) } -} - -private class AccessPathNil extends AccessPath, TAccessPathNil { - private DataFlowType t; - - AccessPathNil() { this = TAccessPathNil(t) } - - DataFlowType getType() { result = t } - - override TypedContent getHead() { none() } - - override AccessPath getTail() { none() } - - override AccessPathFrontNil getFront() { result = TFrontNil(t) } - - override AccessPathApproxNil getApprox() { result = TNil(t) } - - override int length() { result = 0 } - - override string toString() { result = concat(": " + ppReprType(t)) } -} - -private class AccessPathCons extends AccessPath, TAccessPathCons { - private TypedContent head; - private AccessPath tail; - - AccessPathCons() { this = TAccessPathCons(head, tail) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { result = tail } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - pragma[assume_small_delta] - override AccessPathApproxCons getApprox() { - result = TConsNil(head, tail.(AccessPathNil).getType()) - or - result = TConsCons(head, tail.getHead(), this.length()) - or - result = TCons1(head, this.length()) - } - - pragma[assume_small_delta] - override int length() { result = 1 + tail.length() } - - private string toStringImpl(boolean needsSuffix) { - exists(DataFlowType t | - tail = TAccessPathNil(t) and - needsSuffix = false and - result = head.toString() + "]" + concat(" : " + ppReprType(t)) - ) - or - result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) - or - exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | - result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true - or - result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false - ) - or - exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | - result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true - or - result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false - ) - } - - override string toString() { - result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" - or - result = "[" + this.toStringImpl(false) - } -} - -private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { - private TypedContent head1; - private TypedContent head2; - private int len; - - AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } - - override TypedContent getHead() { result = head1 } - - override AccessPath getTail() { - Stage5::consCand(head1, result.getApprox(), _) and - result.getHead() = head2 and - result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head1) } - - override AccessPathApproxCons getApprox() { - result = TConsCons(head1, head2, len) or - result = TCons1(head1, len) - } - - override int length() { result = len } - - override string toString() { - if len = 2 - then result = "[" + head1.toString() + ", " + head2.toString() + "]" - else - result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" - } -} - -private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { - private TypedContent head; - private int len; - - AccessPathCons1() { this = TAccessPathCons1(head, len) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { - Stage5::consCand(head, result.getApprox(), _) and result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - - override int length() { result = len } - - override string toString() { - if len = 1 - then result = "[" + head.toString() + "]" - else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" - } -} - -abstract private class PathNodeImpl extends TPathNode { - /** Gets the `FlowState` of this node. */ - abstract FlowState getState(); - - /** Gets the associated configuration. */ - abstract Configuration getConfiguration(); - - /** Holds if this node is a source. */ - abstract predicate isSource(); - - abstract PathNodeImpl getASuccessorImpl(); - - private PathNodeImpl getASuccessorIfHidden() { - this.isHidden() and - result = this.getASuccessorImpl() - } - - pragma[nomagic] - private PathNodeImpl getANonHiddenSuccessor0() { - result = this.getASuccessorIfHidden*() and - not result.isHidden() - } - - final PathNodeImpl getANonHiddenSuccessor() { - result = this.getASuccessorImpl().getANonHiddenSuccessor0() and - not this.isHidden() - } - - abstract NodeEx getNodeEx(); - - predicate isHidden() { - not this.getConfiguration().includeHiddenNodes() and - ( - hiddenNode(this.getNodeEx().asNode()) and - not this.isSource() and - not this instanceof PathNodeSink - or - this.getNodeEx() instanceof TNodeImplicitRead - ) - } - - string getSourceGroup() { - this.isSource() and - this.getConfiguration().sourceGrouping(this.getNodeEx().asNode(), result) + not singleConfiguration() and + getConfig(state1).isAdditionalFlowStep(node1, node2) and + state2 = state1 } - predicate isFlowSource() { - this.isSource() and not exists(this.getSourceGroup()) - or - this instanceof PathNodeSourceGroup + predicate allowImplicitRead(Node node, ContentSet c) { + any(Configuration config).allowImplicitRead(node, c) } - predicate isFlowSink() { - this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or - this instanceof PathNodeSinkGroup - } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } - private string ppAp() { - this instanceof PathNodeSink and result = "" - or - exists(string s | s = this.(PathNodeMid).getAp().toString() | - if s = "" then result = "" else result = " " + s - ) - } + FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } - private string ppCtx() { - this instanceof PathNodeSink and result = "" - or - result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + predicate sourceGrouping(Node source, string sourceGroup) { + any(Configuration config).sourceGrouping(source, sourceGroup) } - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + predicate sinkGrouping(Node sink, string sinkGroup) { + any(Configuration config).sinkGrouping(sink, sinkGroup) } -} - -/** Holds if `n` can reach a sink. */ -private predicate directReach(PathNodeImpl n) { - n instanceof PathNodeSink or - n instanceof PathNodeSinkGroup or - directReach(n.getANonHiddenSuccessor()) -} - -/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ -private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } -/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { - n1.getANonHiddenSuccessor() = n2 and directReach(n2) + predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() } } -private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) +private import Impl as I +import I /** * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. */ -class PathNode instanceof PathNodeImpl { - PathNode() { reach(this) } - +class PathNode instanceof I::PathNode { /** Gets a textual representation of this element. */ final string toString() { result = super.toString() } @@ -3406,1548 +358,39 @@ class PathNode instanceof PathNodeImpl { } /** Gets the underlying `Node`. */ - final Node getNode() { super.getNodeEx().projectToNode() = result } + final Node getNode() { result = super.getNode() } /** Gets the `FlowState` of this node. */ - final FlowState getState() { result = super.getState() } + final FlowState getState() { result = getState(super.getState()) } /** Gets the associated configuration. */ - final Configuration getConfiguration() { result = super.getConfiguration() } + final Configuration getConfiguration() { result = getConfig(super.getState()) } /** Gets a successor of this node, if any. */ - final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } + final PathNode getASuccessor() { result = super.getASuccessor() } /** Holds if this node is a source. */ final predicate isSource() { super.isSource() } /** Holds if this node is a grouping of source nodes. */ - final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group, _) } + final predicate isSourceGroup(string group) { super.isSourceGroup(group) } /** Holds if this node is a grouping of sink nodes. */ - final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group, _) } + final predicate isSinkGroup(string group) { super.isSinkGroup(group) } } -/** - * Provides the query predicates needed to include a graph in a path-problem query. - */ -module PathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } - - /** Holds if `n` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode n, string key, string val) { - key = "semmle.label" and val = n.toString() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { - Subpaths::subpaths(arg, par, ret, out) - } -} - -/** - * An intermediate flow graph node. This is a triple consisting of a `Node`, - * a `CallContext`, and a `Configuration`. - */ -private class PathNodeMid extends PathNodeImpl, TPathNodeMid { - NodeEx node; - FlowState state; - CallContext cc; - SummaryCtx sc; - AccessPath ap; - Configuration config; - - PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - SummaryCtx getSummaryCtx() { result = sc } - - AccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - private PathNodeMid getSuccMid() { - pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx(), result.getAp()) and - result.getConfiguration() = unbindConf(this.getConfiguration()) - } - - override PathNodeImpl getASuccessorImpl() { - // an intermediate step to another intermediate node - result = this.getSuccMid() - or - // a final step to a sink - result = this.getSuccMid().projectToSink() - } - - override predicate isSource() { - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - } - - predicate isAtSink() { - sinkNode(node, state, config) and - ap instanceof AccessPathNil and - if hasSinkCallCtx(config) - then - // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` - // is exactly what we need to check. This also implies - // `sc instanceof SummaryCtxNone`. - // For `FeatureEqualSourceSinkCallContext` the initial call context was - // set to `CallContextSomeCall` and jumps are disallowed, so - // `cc instanceof CallContextNoCall` never holds. On the other hand, - // in this case there's never any need to enter a call except to identify - // a summary, so the condition in `pathIntoCallable` enforces this, which - // means that `sc instanceof SummaryCtxNone` holds if and only if we are - // in the call context of the source. - sc instanceof SummaryCtxNone or - cc instanceof CallContextNoCall - else any() - } - - PathNodeSink projectToSink() { - this.isAtSink() and - result.getNodeEx() = node and - result.getState() = state and - result.getConfiguration() = unbindConf(config) - } -} - -/** - * A flow graph node corresponding to a sink. This is disjoint from the - * intermediate nodes in order to uniquely correspond to a given sink by - * excluding the `CallContext`. - */ -private class PathNodeSink extends PathNodeImpl, TPathNodeSink { - NodeEx node; - FlowState state; - Configuration config; - - PathNodeSink() { this = TPathNodeSink(node, state, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result = TPathNodeSinkGroup(this.getSinkGroup(), config) - } - - override predicate isSource() { sourceNode(node, state, config) } - - string getSinkGroup() { config.sinkGrouping(node.asNode(), result) } -} - -private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { - string sourceGroup; - Configuration config; - - PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result.getSourceGroup() = sourceGroup and - result.getConfiguration() = config - } - - override predicate isSource() { none() } - - override string toString() { result = sourceGroup } - - override 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 - } -} - -private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { - string sinkGroup; - Configuration config; - - PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { none() } - - override predicate isSource() { none() } - - override string toString() { result = sinkGroup } - - override 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 - } -} - -private predicate pathNode( - PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, - Configuration conf, LocalCallContext localCC -) { - midnode = mid.getNodeEx() and - state = mid.getState() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - midnode.getEnclosingCallable()) and - ap = mid.getAp() -} - -/** - * Holds if data may flow from `mid` to `node`. The last step in or out of - * a callable is recorded by `cc`. - */ -pragma[assume_small_delta] -pragma[nomagic] -private predicate pathStep( - PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap -) { - exists(NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC | - pathNode(mid, midnode, state0, cc, sc, ap, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, true, _, conf, localCC) - ) - or - exists( - AccessPath ap0, NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC - | - pathNode(mid, midnode, state0, cc, sc, ap0, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), conf, - localCC) and - ap0 instanceof AccessPathNil - ) - or - jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - ap = mid.getAp() - or - additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, mid.getConfiguration()) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - pathIntoCallable(mid, node, state, _, cc, sc, _, _) and ap = mid.getAp() - or - pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone - or - pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() -} - -pragma[nomagic] -private predicate pathReadStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - tc = ap0.getHead() and - Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -pragma[nomagic] -private predicate pathStoreStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -private predicate pathOutOfCallable0( - PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa, - Configuration config -) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - apa = mid.getAp().getApprox() and - config = mid.getConfiguration() -} - -pragma[nomagic] -private predicate pathOutOfCallable1( - PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPathApprox apa, Configuration config -) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - pathOutOfCallable0(mid, pos, state, innercc, apa, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() +private predicate hasFlow(Node source, Node sink, Configuration config) { + exists(PathNode source0, PathNode sink0 | + hasFlowPath(source0, sink0, config) and + source0.getNode() = source and + sink0.getNode() = sink ) } -pragma[noinline] -private NodeEx getAnOutNodeFlow( - ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config -) { - result.asNode() = kind.getAnOutNode(call) and - Stage5::revFlow(result, _, apa, config) +private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) { + hasFlowPath(source, sink) and source.getConfiguration() = config } -/** - * Holds if data may flow from `mid` to `out`. The last step of this path - * is a return from a callable and is recorded by `cc`, if needed. - */ -pragma[noinline] -private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { - exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | - pathOutOfCallable1(mid, call, kind, state, cc, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} +private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) } -/** - * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. - */ -pragma[noinline] -private predicate pathIntoArg( - PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(ArgNodeEx arg, ArgumentPosition apos | - pathNode(mid, arg, state, cc, _, ap, config, _) and - arg.asNode().(ArgNode).argumentOf(call, apos) and - apa = ap.getApprox() and - parameterMatch(ppos, apos) - ) -} - -pragma[nomagic] -private predicate parameterCand( - DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::revFlow(p, _, apa, config) and - p.isParameterOf(callable, pos) - ) -} - -pragma[nomagic] -private predicate pathIntoCallable0( - PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, AccessPath ap, Configuration config -) { - exists(AccessPathApprox apa | - pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - callable = resolveCall(call, outercc) and - parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa), - pragma[only_bind_into](config)) - ) -} - -/** - * Holds if data may flow from `mid` to `p` through `call`. The contexts - * before and after entering the callable are `outercc` and `innercc`, - * respectively. - */ -pragma[nomagic] -private predicate pathIntoCallable( - PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, - SummaryCtx sc, DataFlowCall call, Configuration config -) { - exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | - pathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - ( - sc = TSummaryCtxSome(p, state, ap) - or - not exists(TSummaryCtxSome(p, state, ap)) and - sc = TSummaryCtxNone() and - // When the call contexts of source and sink needs to match then there's - // never any reason to enter a callable except to find a summary. See also - // the comment in `PathNodeMid::isAtSink`. - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) -} - -/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ -pragma[nomagic] -private predicate paramFlowsThrough( - ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, - AccessPathApprox apa, Configuration config -) { - exists(RetNodeEx ret | - pathNode(_, ret, state, cc, sc, ap, config, _) and - kind = ret.getKind() and - apa = ap.getApprox() and - parameterFlowThroughAllowed(sc.getParamNode(), kind) - ) -} - -pragma[nomagic] -private predicate pathThroughCallable0( - DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(CallContext innercc, SummaryCtx sc | - pathIntoCallable(mid, _, _, cc, innercc, sc, call, config) and - paramFlowsThrough(kind, state, innercc, sc, ap, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` through a callable to the node `out`. - * The context `cc` is restored to its value prior to entering the callable. - */ -pragma[noinline] -private predicate pathThroughCallable( - PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap -) { - exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config | - pathThroughCallable0(call, mid, kind, state, cc, ap, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -private module Subpaths { - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths01( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - exists(Configuration config | - pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and - pathIntoCallable(arg, par, _, _, innercc, sc, _, config) and - paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, - pragma[only_bind_into](apout), _, unbindConf(config)) and - not arg.isHidden() - ) - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `sout`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths02( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and - out.asNode() = kind.getAnOutNode(_) - } - - pragma[nomagic] - private Configuration getPathNodeConf(PathNodeImpl n) { result = n.getConfiguration() } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple. - */ - pragma[nomagic] - private predicate subpaths03( - PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, AccessPath apout - ) { - exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | - subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and - pathNode(ret, retnode, sout, innercc, sc, apout, unbindConf(getPathNodeConf(arg)), _) and - kind = retnode.getKind() - ) - } - - private PathNodeImpl localStepToHidden(PathNodeImpl n) { - n.getASuccessorImpl() = result and - result.isHidden() and - exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | - localFlowBigStep(n1, _, n2, _, _, _, _, _) or - store(n1, _, n2, _, _) or - readSet(n1, _, n2, _) - ) - } - - pragma[nomagic] - private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { - succ = pred.getANonHiddenSuccessor() and - succNode = succ.getNodeEx() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { - exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | - pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and - subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and - hasSuccessor(pragma[only_bind_into](arg), par, p) and - not ret.isHidden() and - pathNode(out0, o, sout, _, _, apout, _, _) - | - out = out0 or out = out0.projectToSink() - ) - } - - /** - * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. - */ - predicate retReach(PathNodeImpl n) { - exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) - or - exists(PathNodeImpl mid | - retReach(mid) and - n.getANonHiddenSuccessor() = mid and - not subpaths(_, mid, _, _) - ) - } -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -private predicate hasFlowPath( - PathNodeImpl flowsource, PathNodeImpl flowsink, Configuration configuration -) { - flowsource.isFlowSource() and - flowsource.getConfiguration() = configuration and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.isFlowSink() -} - -private predicate flowsTo( - PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink, - Configuration configuration -) { - flowsource.isSource() and - flowsource.getConfiguration() = configuration and - flowsource.getNodeEx().asNode() = source and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.getNodeEx().asNode() = sink -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -predicate flowsTo(Node source, Node sink, Configuration configuration) { - flowsTo(_, _, source, sink, configuration) -} - -private predicate finalStats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples -) { - fwd = true and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and - tuples = count(PathNodeImpl pn) - or - fwd = false and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and - tuples = count(PathNode pn) -} - -/** - * INTERNAL: Only for debugging. - * - * Calculates per-stage metrics for data flow. - */ -predicate stageStats( - int n, string stage, int nodes, int fields, int conscand, int states, int tuples, - Configuration config -) { - stage = "1 Fwd" and - n = 10 and - Stage1::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "1 Rev" and - n = 15 and - Stage1::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Fwd" and - n = 20 and - Stage2::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Rev" and - n = 25 and - Stage2::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Fwd" and - n = 30 and - Stage3::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Rev" and - n = 35 and - Stage3::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Fwd" and - n = 40 and - Stage4::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Rev" and - n = 45 and - Stage4::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Fwd" and - n = 50 and - Stage5::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Rev" and - n = 55 and - Stage5::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) - or - stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) -} - -private module FlowExploration { - private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - jumpStep(node1, node2, config) - or - additionalJumpStep(node1, node2, config) - or - additionalJumpStateStep(node1, _, node2, _, config) - or - // flow into callable - viableParamArgEx(_, node2, node1) - or - // flow out of a callable - viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) - | - c1 = node1.getEnclosingCallable() and - c2 = node2.getEnclosingCallable() and - c1 != c2 - ) - } - - private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSource(n) or config.isSource(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSrc(mid, config) and callableStep(mid, c, config) - ) - } - - private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSink(n) or config.isSink(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSink(mid, config) and callableStep(c, mid, config) - ) - } - - private newtype TCallableExt = - TCallable(DataFlowCallable c, Configuration config) { - interestingCallableSrc(c, config) or - interestingCallableSink(c, config) - } or - TCallableSrc() or - TCallableSink() - - private predicate callableExtSrc(TCallableSrc src) { any() } - - private predicate callableExtSink(TCallableSink sink) { any() } - - private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { - exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | - callableStep(c1, c2, config) and - ce1 = TCallable(c1, pragma[only_bind_into](config)) and - ce2 = TCallable(c2, pragma[only_bind_into](config)) - ) - or - exists(Node n, Configuration config | - ce1 = TCallableSrc() and - (config.isSource(n) or config.isSource(n, _)) and - ce2 = TCallable(getNodeEnclosingCallable(n), config) - ) - or - exists(Node n, Configuration config | - ce2 = TCallableSink() and - (config.isSink(n) or config.isSink(n, _)) and - ce1 = TCallable(getNodeEnclosingCallable(n), config) - ) - } - - private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { - callableExtStepFwd(ce2, ce1) - } - - private int distSrcExt(TCallableExt c) = - shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) - - private int distSinkExt(TCallableExt c) = - shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) - - private int distSrc(DataFlowCallable c, Configuration config) { - result = distSrcExt(TCallable(c, config)) - 1 - } - - private int distSink(DataFlowCallable c, Configuration config) { - result = distSinkExt(TCallable(c, config)) - 1 - } - - private newtype TPartialAccessPath = - TPartialNil(DataFlowType t) or - TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first - * element of the list and its length are tracked. If data flows from a source to - * a given node with a given `AccessPath`, this indicates the sequence of - * dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ - private class PartialAccessPath extends TPartialAccessPath { - abstract string toString(); - - TypedContent getHead() { this = TPartialCons(result, _) } - - int len() { - this = TPartialNil(_) and result = 0 - or - this = TPartialCons(_, result) - } - - DataFlowType getType() { - this = TPartialNil(result) - or - exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) - } - } - - private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { - override string toString() { - exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) - } - } - - private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { - override string toString() { - exists(TypedContent tc, int len | this = TPartialCons(tc, len) | - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private newtype TRevPartialAccessPath = - TRevPartialNil() or - TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `Content`s, but only the first - * element of the list and its length are tracked. - */ - private class RevPartialAccessPath extends TRevPartialAccessPath { - abstract string toString(); - - Content getHead() { this = TRevPartialCons(result, _) } - - int len() { - this = TRevPartialNil() and result = 0 - or - this = TRevPartialCons(_, result) - } - } - - private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { - override string toString() { result = "" } - } - - private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { - override string toString() { - exists(Content c, int len | this = TRevPartialCons(c, len) | - if len = 1 - then result = "[" + c.toString() + "]" - else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private predicate relevantState(FlowState state) { - sourceNode(_, state, _) or - sinkNode(_, state, _) or - additionalLocalStateStep(_, state, _, _, _) or - additionalLocalStateStep(_, _, _, state, _) or - additionalJumpStateStep(_, state, _, _, _) or - additionalJumpStateStep(_, _, _, state, _) - } - - private newtype TSummaryCtx1 = - TSummaryCtx1None() or - TSummaryCtx1Param(ParamNodeEx p) - - private newtype TSummaryCtx2 = - TSummaryCtx2None() or - TSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TSummaryCtx3 = - TSummaryCtx3None() or - TSummaryCtx3Some(PartialAccessPath ap) - - private newtype TRevSummaryCtx1 = - TRevSummaryCtx1None() or - TRevSummaryCtx1Some(ReturnPosition pos) - - private newtype TRevSummaryCtx2 = - TRevSummaryCtx2None() or - TRevSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TRevSummaryCtx3 = - TRevSummaryCtx3None() or - TRevSummaryCtx3Some(RevPartialAccessPath ap) - - private newtype TPartialPathNode = - TPartialPathNodeFwd( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = TPartialNil(node.getDataFlowType()) and - exists(config.explorationLimit()) - or - partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap, config) and - distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() - } or - TPartialPathNodeRev( - NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, - RevPartialAccessPath ap, Configuration config - ) { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() and - exists(config.explorationLimit()) - or - revPartialPathStep(_, node, state, sc1, sc2, sc3, ap, config) and - not clearsContentEx(node, ap.getHead()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead()) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() - } - - pragma[nomagic] - private predicate partialPathNodeMk0( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap, config) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - not clearsContentEx(node, ap.getHead().getContent()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead().getContent()) - ) and - if node.asNode() instanceof CastingNode - then compatibleTypes(node.getDataFlowType(), ap.getType()) - else any() - } - - /** - * A `Node` augmented with a call context, an access path, and a configuration. - */ - class PartialPathNode extends TPartialPathNode { - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() - } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - /** Gets the underlying `Node`. */ - final Node getNode() { this.getNodeEx().projectToNode() = result } - - FlowState getState() { none() } - - private NodeEx getNodeEx() { - result = this.(PartialPathNodeFwd).getNodeEx() or - result = this.(PartialPathNodeRev).getNodeEx() - } - - /** Gets the associated configuration. */ - Configuration getConfiguration() { none() } - - /** Gets a successor of this node, if any. */ - PartialPathNode getASuccessor() { none() } - - /** - * Gets the approximate distance to the nearest source measured in number - * of interprocedural steps. - */ - int getSourceDistance() { - result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - /** - * Gets the approximate distance to the nearest sink measured in number - * of interprocedural steps. - */ - int getSinkDistance() { - result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - private string ppAp() { - exists(string s | - s = this.(PartialPathNodeFwd).getAp().toString() or - s = this.(PartialPathNodeRev).getAp().toString() - | - if s = "" then result = "" else result = " " + s - ) - } - - private string ppCtx() { - result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" - } - - /** Holds if this is a source in a forward-flow path. */ - predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } - - /** Holds if this is a sink in a reverse-flow path. */ - predicate isRevSink() { this.(PartialPathNodeRev).isSink() } - } - - /** - * Provides the query predicates needed to include a graph in a path-problem query. - */ - module PartialPathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } - } - - private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { - NodeEx node; - FlowState state; - CallContext cc; - TSummaryCtx1 sc1; - TSummaryCtx2 sc2; - TSummaryCtx3 sc3; - PartialAccessPath ap; - Configuration config; - - PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - TSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TSummaryCtx3 getSummaryCtx3() { result = sc3 } - - PartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeFwd getASuccessor() { - partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp(), - result.getConfiguration()) - } - - predicate isSource() { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap instanceof TPartialNil - } - } - - private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { - NodeEx node; - FlowState state; - TRevSummaryCtx1 sc1; - TRevSummaryCtx2 sc2; - TRevSummaryCtx3 sc3; - RevPartialAccessPath ap; - Configuration config; - - PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } - - RevPartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeRev getASuccessor() { - revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), - this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp(), this.getConfiguration()) - } - - predicate isSink() { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() - } - } - - private predicate partialPathStep( - PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, - TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and - ( - localFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - ) - or - jumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - partialPathStoreStep(mid, _, _, node, ap) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(PartialAccessPath ap0, TypedContent tc | - partialPathReadStep(mid, ap0, tc, node, cc, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsFwd(ap, tc, ap0, config) - ) - or - partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap, config) - or - partialPathOutOfCallable(mid, node, state, cc, ap, config) and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() - or - partialPathThroughCallable(mid, node, state, cc, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - bindingset[result, i] - private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } - - pragma[inline] - private predicate partialPathStoreStep( - PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, - PartialAccessPath ap2 - ) { - exists(NodeEx midNode, DataFlowType contentType | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - store(midNode, tc, node, contentType, mid.getConfiguration()) and - ap2.getHead() = tc and - ap2.len() = unbindInt(ap1.len() + 1) and - compatibleTypes(ap1.getType(), contentType) - ) - } - - pragma[nomagic] - private predicate apConsFwd( - PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeFwd mid | - partialPathStoreStep(mid, ap1, tc, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate partialPathReadStep( - PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc, - Configuration config - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and - ap.getHead() = tc and - pragma[only_bind_into](config) = mid.getConfiguration() and - cc = mid.getCallContext() - ) - } - - private predicate partialPathOutOfCallable0( - PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, - PartialAccessPath ap, Configuration config - ) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - ap = mid.getAp() and - config = mid.getConfiguration() - } - - pragma[nomagic] - private predicate partialPathOutOfCallable1( - PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - partialPathOutOfCallable0(mid, pos, state, innercc, ap, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) - } - - private predicate partialPathOutOfCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(ReturnKindExt kind, DataFlowCall call | - partialPathOutOfCallable1(mid, call, kind, state, cc, ap, config) - | - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[noinline] - private predicate partialPathIntoArg( - PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ArgNode arg, ArgumentPosition apos | - arg = mid.getNodeEx().asNode() and - state = mid.getState() and - cc = mid.getCallContext() and - arg.argumentOf(call, apos) and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate partialPathIntoCallable0( - PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - partialPathIntoArg(mid, pos, state, outercc, call, ap, config) and - callable = resolveCall(call, outercc) - } - - private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, - CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ParameterPosition pos, DataFlowCallable callable | - partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - sc1 = TSummaryCtx1Param(p) and - sc2 = TSummaryCtx2Some(state) and - sc3 = TSummaryCtx3Some(ap) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) - } - - pragma[nomagic] - private predicate paramFlowsThroughInPartialPath( - ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeFwd mid, RetNodeEx ret | - mid.getNodeEx() = ret and - kind = ret.getKind() and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() and - ap = mid.getAp() - ) - } - - pragma[noinline] - private predicate partialPathThroughCallable0( - DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | - partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _, config) and - paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap, config) - ) - } - - private predicate partialPathThroughCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ReturnKindExt kind | - partialPathThroughCallable0(call, mid, kind, state, cc, ap, config) and - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[nomagic] - private predicate revPartialPathStep( - PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, - TRevSummaryCtx3 sc3, RevPartialAccessPath ap, Configuration config - ) { - localFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - jumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - revPartialPathReadStep(mid, _, _, node, ap) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(RevPartialAccessPath ap0, Content c | - revPartialPathStoreStep(mid, ap0, c, node, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsRev(ap, c, ap0, config) - ) - or - exists(ParamNodeEx p | - mid.getNodeEx() = p and - viableParamArgEx(_, p, node) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - or - exists(ReturnPosition pos | - revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap, config) and - pos = getReturnPosition(node.asNode()) - ) - or - revPartialPathThroughCallable(mid, node, state, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - pragma[inline] - private predicate revPartialPathReadStep( - PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, - RevPartialAccessPath ap2 - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - read(node, c, midNode, mid.getConfiguration()) and - ap2.getHead() = c and - ap2.len() = unbindInt(ap1.len() + 1) - ) - } - - pragma[nomagic] - private predicate apConsRev( - RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeRev mid | - revPartialPathReadStep(mid, ap1, c, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathStoreStep( - PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config - ) { - exists(NodeEx midNode, TypedContent tc | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - store(node, tc, midNode, _, config) and - ap.getHead() = c and - config = mid.getConfiguration() and - tc.getContent() = c - ) - } - - pragma[nomagic] - private predicate revPartialPathIntoReturn( - PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, - TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap, - Configuration config - ) { - exists(NodeEx out | - mid.getNodeEx() = out and - mid.getState() = state and - viableReturnPosOutEx(call, pos, out) and - sc1 = TRevSummaryCtx1Some(pos) and - sc2 = TRevSummaryCtx2Some(state) and - sc3 = TRevSummaryCtx3Some(ap) and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathFlowsThrough( - ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, - TRevSummaryCtx3Some sc3, RevPartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | - mid.getNodeEx() = p and - mid.getState() = state and - p.getPosition() = ppos and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable0( - DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, - RevPartialAccessPath ap, Configuration config - ) { - exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | - revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _, config) and - revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap, config) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ArgumentPosition pos | - revPartialPathThroughCallable0(call, mid, pos, state, ap, config) and - node.asNode().(ArgNode).argumentOf(call, pos) - ) - } -} - -import FlowExploration - -private predicate partialFlow( - PartialPathNode source, PartialPathNode node, Configuration configuration -) { - source.getConfiguration() = configuration and - source.isFwdSource() and - node = source.getASuccessor+() -} - -private predicate revPartialFlow( - PartialPathNode node, PartialPathNode sink, Configuration configuration -) { - sink.getConfiguration() = configuration and - sink.isRevSink() and - node.getASuccessor+() = sink -} +predicate flowsTo = hasFlow/3; diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/FlowSummaryImpl.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/FlowSummaryImpl.qll index 85ffeb62e5f..468ed73e81a 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/FlowSummaryImpl.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/FlowSummaryImpl.qll @@ -248,7 +248,9 @@ module Public { /** * Holds if all the summaries that apply to `this` are auto generated and not manually created. */ - final predicate isAutoGenerated() { this.hasProvenance("generated") and not this.isManual() } + final predicate isAutoGenerated() { + this.hasProvenance(["generated", "ai-generated"]) and not this.isManual() + } /** * Holds if there exists a manual summary that applies to `this`. @@ -268,7 +270,7 @@ module Public { /** * Holds if the neutral is auto generated. */ - predicate isAutoGenerated() { neutralElement(this, "generated") } + predicate isAutoGenerated() { neutralElement(this, ["generated", "ai-generated"]) } /** * Holds if there exists a manual neutral that applies to `this`. @@ -1202,11 +1204,11 @@ module Private { } private string renderProvenance(SummarizedCallable c) { - if c.isAutoGenerated() then result = "generated" else result = "manual" + if c.isManual() then result = "manual" else c.hasProvenance(result) } private string renderProvenanceNeutral(NeutralCallable c) { - if c.isAutoGenerated() then result = "generated" else result = "manual" + if c.isManual() then result = "manual" else c.hasProvenance(result) } /** diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/tainttracking1/TaintTracking.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/tainttracking1/TaintTracking.qll new file mode 100644 index 00000000000..7333264298e --- /dev/null +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/tainttracking1/TaintTracking.qll @@ -0,0 +1,63 @@ +/** + * Provides classes for performing local (intra-procedural) and + * global (inter-procedural) taint-tracking analyses. + */ + +import TaintTrackingParameter::Public +private import TaintTrackingParameter::Private + +private module AddTaintDefaults implements +DataFlowInternal::FullStateConfigSig { + import Config + + predicate isBarrier(DataFlow::Node node) { + Config::isBarrier(node) or defaultTaintSanitizer(node) + } + + predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + Config::isAdditionalFlowStep(node1, node2) or + defaultAdditionalTaintStep(node1, node2) + } + + predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) { + Config::allowImplicitRead(node, c) + or + ( + Config::isSink(node, _) or + Config::isAdditionalFlowStep(node, _) or + Config::isAdditionalFlowStep(node, _, _, _) + ) and + defaultImplicitTaintRead(node, c) + } +} + +/** + * Constructs a standard taint tracking computation. + */ +module Make implements DataFlow::DataFlowSig { + private module Config0 implements DataFlowInternal::FullStateConfigSig { + import DataFlowInternal::DefaultState + import Config + } + + private module C implements DataFlowInternal::FullStateConfigSig { + import AddTaintDefaults + } + + import DataFlowInternal::Impl +} + +/** + * Constructs a taint tracking computation using flow state. + */ +module MakeWithState implements DataFlow::DataFlowSig { + private module Config0 implements DataFlowInternal::FullStateConfigSig { + import Config + } + + private module C implements DataFlowInternal::FullStateConfigSig { + import AddTaintDefaults + } + + import DataFlowInternal::Impl +} diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/tainttracking1/TaintTrackingParameter.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/tainttracking1/TaintTrackingParameter.qll index 275149a07d4..6fa484bea77 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/tainttracking1/TaintTrackingParameter.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/tainttracking1/TaintTrackingParameter.qll @@ -2,5 +2,6 @@ import semmle.code.csharp.dataflow.internal.TaintTrackingPublic as Public module Private { import semmle.code.csharp.dataflow.DataFlow::DataFlow as DataFlow + import semmle.code.csharp.dataflow.internal.DataFlowImpl as DataFlowInternal import semmle.code.csharp.dataflow.internal.TaintTrackingPrivate } diff --git a/csharp/ql/lib/semmle/code/csharp/frameworks/EntityFramework.qll b/csharp/ql/lib/semmle/code/csharp/frameworks/EntityFramework.qll index 1bd86b98ff2..c35db06d214 100644 --- a/csharp/ql/lib/semmle/code/csharp/frameworks/EntityFramework.qll +++ b/csharp/ql/lib/semmle/code/csharp/frameworks/EntityFramework.qll @@ -86,6 +86,8 @@ module EntityFramework { abstract class EFSummarizedCallable extends SummarizedCallable { bindingset[this] EFSummarizedCallable() { any() } + + override predicate hasProvenance(string provenance) { provenance = "manual" } } private class DbSetAddOrUpdateRequiredSummaryComponentStack extends RequiredSummaryComponentStack { diff --git a/csharp/ql/lib/semmle/code/dotnet/Declaration.qll b/csharp/ql/lib/semmle/code/dotnet/Declaration.qll index 8e45a06eaca..573a690c0ec 100644 --- a/csharp/ql/lib/semmle/code/dotnet/Declaration.qll +++ b/csharp/ql/lib/semmle/code/dotnet/Declaration.qll @@ -83,6 +83,9 @@ class Member extends Declaration, @dotnet_member { /** Holds if this member is declared `required`. */ predicate isRequired() { none() } + /** Holds if this member is declared `file` local. */ + predicate isFile() { none() } + /** * Holds if this member has name `name` and is defined in type `type` * with namespace `namespace`. diff --git a/csharp/ql/src/Likely Bugs/StaticFieldWrittenByInstance.ql b/csharp/ql/src/Likely Bugs/StaticFieldWrittenByInstance.ql index 3912a99d779..4acea4a35b3 100644 --- a/csharp/ql/src/Likely Bugs/StaticFieldWrittenByInstance.ql +++ b/csharp/ql/src/Likely Bugs/StaticFieldWrittenByInstance.ql @@ -1,6 +1,6 @@ /** * @name Static field written by instance method - * @description Finds instance methods that write static fields. + * @description Finds instance methods and properties that write to static fields. * This is tricky to get right if multiple instances are being manipulated, * and generally bad practice. * @kind problem @@ -14,12 +14,12 @@ import csharp -from FieldWrite fw, Field f, Callable m +from FieldWrite fw, Field f, Callable c where fw.getTarget() = f and f.isStatic() and - m = fw.getEnclosingCallable() and - not m.(Member).isStatic() and - f.getDeclaringType() = m.getDeclaringType() and - m.fromSource() -select fw.(VariableAccess), "Write to static field from instance method or constructor." + c = fw.getEnclosingCallable() and + not [c.(Member), c.(Accessor).getDeclaration()].isStatic() and + f.getDeclaringType() = c.getDeclaringType() and + c.fromSource() +select fw.(VariableAccess), "Write to static field from instance method, property, or constructor." diff --git a/csharp/ql/test/library-tests/csharp11/FileScoped1.cs b/csharp/ql/test/library-tests/csharp11/FileScoped1.cs new file mode 100644 index 00000000000..47980e597a3 --- /dev/null +++ b/csharp/ql/test/library-tests/csharp11/FileScoped1.cs @@ -0,0 +1,25 @@ +file interface I1 { } + +file interface I2 { } + +file class C1 : I1 { } + +public class C2 { } + +public class C3 : I2 { } + +file interface IC { } + +file class C4 { } + +file class C5 : C4 { } + +file struct S1 { } + +file enum E1 { } + +file delegate void D1(); + +file record R1 { } + +file record struct RS1 { } \ No newline at end of file diff --git a/csharp/ql/test/library-tests/csharp11/FileScoped2.cs b/csharp/ql/test/library-tests/csharp11/FileScoped2.cs new file mode 100644 index 00000000000..fd39f8ee124 --- /dev/null +++ b/csharp/ql/test/library-tests/csharp11/FileScoped2.cs @@ -0,0 +1,23 @@ +file interface I1 { } + +public interface I2 { } + +file class C1 { } + +file class C2 : I2 { } + +file class IC { } + +file class C4 { } + +file class C5 : C4 { } + +file struct S1 { } + +file enum E1 { } + +file delegate void D1(); + +file record R1 { } + +file record struct RS1 { } diff --git a/csharp/ql/test/library-tests/csharp11/FileScoped3.cs b/csharp/ql/test/library-tests/csharp11/FileScoped3.cs new file mode 100644 index 00000000000..87b3a85b934 --- /dev/null +++ b/csharp/ql/test/library-tests/csharp11/FileScoped3.cs @@ -0,0 +1,7 @@ +namespace TestFileScoped; + +file interface I10 { } + +file class C10 { } + +public class C11 : I10 { } \ No newline at end of file diff --git a/csharp/ql/test/library-tests/csharp11/FileScoped4.cs b/csharp/ql/test/library-tests/csharp11/FileScoped4.cs new file mode 100644 index 00000000000..1b889d4fa89 --- /dev/null +++ b/csharp/ql/test/library-tests/csharp11/FileScoped4.cs @@ -0,0 +1,7 @@ +namespace TestFileScoped; + +public interface I10 { } + +file class C10 { } + +file class C11 : I10 { } \ No newline at end of file diff --git a/csharp/ql/test/library-tests/csharp11/PrintAst.expected b/csharp/ql/test/library-tests/csharp11/PrintAst.expected index 3ef77ba3864..f68e6dffaca 100644 --- a/csharp/ql/test/library-tests/csharp11/PrintAst.expected +++ b/csharp/ql/test/library-tests/csharp11/PrintAst.expected @@ -216,6 +216,103 @@ CheckedOperators.cs: # 55| 0: [TypeMention] short # 55| 1: [PropertyCall] access to property Value # 55| -1: [ParameterAccess] access to parameter n +FileScoped1.cs: +# 1| [Interface] I1 +# 3| [Interface] I2 +# 5| [Class] C1 +#-----| 3: (Base types) +# 5| 1: [TypeMention] I1 +# 7| [Class] C2 +# 9| [Class] C3 +#-----| 3: (Base types) +# 9| 1: [TypeMention] I2 +# 11| [Interface] IC +# 13| [Class] C4<> +#-----| 1: (Type parameters) +# 13| 0: [TypeParameter] T +# 15| [Class] C5<> +#-----| 1: (Type parameters) +# 15| 0: [TypeParameter] S +#-----| 3: (Base types) +# 15| 0: [TypeMention] C4 +# 15| 1: [TypeMention] S +# 17| [Struct] S1 +# 19| [Enum] E1 +# 21| [DelegateType] D1 +# 23| [RecordClass] R1 +# 23| 12: [NEOperator] != +#-----| 2: (Parameters) +# 23| 0: [Parameter] left +# 23| 1: [Parameter] right +# 23| 13: [EQOperator] == +#-----| 2: (Parameters) +# 23| 0: [Parameter] left +# 23| 1: [Parameter] right +# 23| 14: [Property] EqualityContract +# 23| 3: [Getter] get_EqualityContract +# 25| [RecordStruct] RS1 +# 25| 10: [NEOperator] != +#-----| 2: (Parameters) +# 25| 0: [Parameter] left +# 25| 1: [Parameter] right +# 25| 11: [EQOperator] == +#-----| 2: (Parameters) +# 25| 0: [Parameter] left +# 25| 1: [Parameter] right +FileScoped2.cs: +# 1| [Interface] I1 +# 3| [Interface] I2 +# 5| [Class] C1 +# 7| [Class] C2 +#-----| 3: (Base types) +# 7| 1: [TypeMention] I2 +# 9| [Class] IC +# 11| [Class] C4<> +#-----| 1: (Type parameters) +# 11| 0: [TypeParameter] T +# 13| [Class] C5<> +#-----| 1: (Type parameters) +# 13| 0: [TypeParameter] S +#-----| 3: (Base types) +# 13| 0: [TypeMention] C4 +# 13| 1: [TypeMention] S +# 15| [Struct] S1 +# 17| [Enum] E1 +# 19| [DelegateType] D1 +# 21| [RecordClass] R1 +# 21| 12: [NEOperator] != +#-----| 2: (Parameters) +# 21| 0: [Parameter] left +# 21| 1: [Parameter] right +# 21| 13: [EQOperator] == +#-----| 2: (Parameters) +# 21| 0: [Parameter] left +# 21| 1: [Parameter] right +# 21| 14: [Property] EqualityContract +# 21| 3: [Getter] get_EqualityContract +# 23| [RecordStruct] RS1 +# 23| 10: [NEOperator] != +#-----| 2: (Parameters) +# 23| 0: [Parameter] left +# 23| 1: [Parameter] right +# 23| 11: [EQOperator] == +#-----| 2: (Parameters) +# 23| 0: [Parameter] left +# 23| 1: [Parameter] right +FileScoped3.cs: +# 1| [NamespaceDeclaration] namespace ... { ... } +# 3| 1: [Interface] I10 +# 5| 2: [Class] C10 +# 7| 3: [Class] C11 +#-----| 3: (Base types) +# 7| 1: [TypeMention] I10 +FileScoped4.cs: +# 1| [NamespaceDeclaration] namespace ... { ... } +# 3| 1: [Interface] I10 +# 5| 2: [Class] C10 +# 7| 3: [Class] C11 +#-----| 3: (Base types) +# 7| 1: [TypeMention] I10 GenericAttribute.cs: # 3| [GenericAssemblyAttribute] [assembly: MyGeneric(...)] # 3| 0: [TypeMention] MyGenericAttribute @@ -954,14 +1051,14 @@ StaticInterfaceMembers.cs: # 5| 0: [Parameter] other # 5| -1: [TypeMention] T # 5| 4: [ParameterAccess] access to parameter other -# 7| 6: [Method] Add +# 7| 6: [AddOperator] + # 7| -1: [TypeMention] T #-----| 2: (Parameters) # 7| 0: [Parameter] left # 7| -1: [TypeMention] T # 7| 1: [Parameter] right # 7| -1: [TypeMention] T -# 9| 7: [Method] Subtract +# 9| 7: [SubOperator] - # 9| -1: [TypeMention] T #-----| 2: (Parameters) # 9| 0: [Parameter] left @@ -969,121 +1066,265 @@ StaticInterfaceMembers.cs: # 9| 1: [Parameter] right # 9| -1: [TypeMention] T # 9| 4: [ParameterAccess] access to parameter left -# 11| 8: [Method] Zero -# 11| -1: [TypeMention] T -# 11| 4: [DefaultValueExpr] default(...) -# 11| 0: [TypeAccess] access to type T -# 11| 0: [TypeMention] T -# 14| [Class] Complex +# 11| 8: [ExplicitConversionOperator] explicit conversion +# 11| -1: [TypeMention] int +#-----| 2: (Parameters) +# 11| 0: [Parameter] n +# 11| -1: [TypeMention] T +# 13| 9: [ExplicitConversionOperator] explicit conversion +# 13| -1: [TypeMention] short +#-----| 2: (Parameters) +# 13| 0: [Parameter] n +# 13| -1: [TypeMention] T +# 15| 10: [Method] Inc +# 15| -1: [TypeMention] T +#-----| 2: (Parameters) +# 15| 0: [Parameter] other +# 15| -1: [TypeMention] T +# 17| 11: [Method] Dec +# 17| -1: [TypeMention] T +#-----| 2: (Parameters) +# 17| 0: [Parameter] other +# 17| -1: [TypeMention] T +# 17| 4: [ParameterAccess] access to parameter other +# 19| 12: [Method] Add +# 19| -1: [TypeMention] T +#-----| 2: (Parameters) +# 19| 0: [Parameter] left +# 19| -1: [TypeMention] T +# 19| 1: [Parameter] right +# 19| -1: [TypeMention] T +# 21| 13: [Method] Subtract +# 21| -1: [TypeMention] T +#-----| 2: (Parameters) +# 21| 0: [Parameter] left +# 21| -1: [TypeMention] T +# 21| 1: [Parameter] right +# 21| -1: [TypeMention] T +# 21| 4: [ParameterAccess] access to parameter left +# 23| 14: [Method] Zero +# 23| -1: [TypeMention] T +# 23| 4: [DefaultValueExpr] default(...) +# 23| 0: [TypeAccess] access to type T +# 23| 0: [TypeMention] T +# 26| [Class] Complex #-----| 3: (Base types) -# 16| 4: [Property] Real -# 16| -1: [TypeMention] double -# 16| 2: [AssignExpr] ... = ... -# 16| 0: [PropertyCall] access to property Real -# 16| 1: [DoubleLiteral] 0 -# 16| 3: [Getter] get_Real -# 16| 4: [Setter] set_Real +# 28| 4: [Property] Real +# 28| -1: [TypeMention] double +# 28| 2: [AssignExpr] ... = ... +# 28| 0: [PropertyCall] access to property Real +# 28| 1: [DoubleLiteral] 0 +# 28| 3: [Getter] get_Real +# 28| 4: [Setter] set_Real #-----| 2: (Parameters) -# 16| 0: [Parameter] value -# 17| 5: [Property] Imaginary -# 17| -1: [TypeMention] double -# 17| 2: [AssignExpr] ... = ... -# 17| 0: [PropertyCall] access to property Imaginary -# 17| 1: [DoubleLiteral] 0 -# 17| 3: [Getter] get_Imaginary -# 17| 4: [Setter] set_Imaginary +# 28| 0: [Parameter] value +# 29| 5: [Property] Imaginary +# 29| -1: [TypeMention] double +# 29| 2: [AssignExpr] ... = ... +# 29| 0: [PropertyCall] access to property Imaginary +# 29| 1: [DoubleLiteral] 0 +# 29| 3: [Getter] get_Imaginary +# 29| 4: [Setter] set_Imaginary #-----| 2: (Parameters) -# 17| 0: [Parameter] value -# 19| 6: [InstanceConstructor] Complex -# 19| 4: [BlockStmt] {...} -# 21| 7: [Method] Zero -# 21| -1: [TypeMention] Complex -# 21| 4: [ObjectCreation] object creation of type Complex -# 21| 0: [TypeMention] Complex -# 23| 8: [IncrementOperator] ++ -# 23| -1: [TypeMention] Complex -#-----| 2: (Parameters) -# 23| 0: [Parameter] other -# 23| -1: [TypeMention] Complex -# 24| 4: [ObjectCreation] object creation of type Complex -# 24| -2: [TypeMention] Complex -# 24| -1: [ObjectInitializer] { ..., ... } -# 24| 0: [MemberInitializer] ... = ... -# 24| 0: [PropertyCall] access to property Real -# 24| 1: [AddExpr] ... + ... -# 24| 0: [PropertyCall] access to property Real -# 24| -1: [ParameterAccess] access to parameter other -# 24| 1: [DoubleLiteral] 1 -# 24| 1: [MemberInitializer] ... = ... -# 24| 0: [PropertyCall] access to property Imaginary -# 24| 1: [PropertyCall] access to property Imaginary -# 24| -1: [ParameterAccess] access to parameter other -# 26| 9: [DecrementOperator] -- -# 26| -1: [TypeMention] Complex -#-----| 2: (Parameters) -# 26| 0: [Parameter] other -# 26| -1: [TypeMention] Complex -# 27| 4: [ObjectCreation] object creation of type Complex -# 27| -2: [TypeMention] Complex -# 27| -1: [ObjectInitializer] { ..., ... } -# 27| 0: [MemberInitializer] ... = ... -# 27| 0: [PropertyCall] access to property Real -# 27| 1: [SubExpr] ... - ... -# 27| 0: [PropertyCall] access to property Real -# 27| -1: [ParameterAccess] access to parameter other -# 27| 1: [DoubleLiteral] 1 -# 27| 1: [MemberInitializer] ... = ... -# 27| 0: [PropertyCall] access to property Imaginary -# 27| 1: [PropertyCall] access to property Imaginary -# 27| -1: [ParameterAccess] access to parameter other -# 29| 10: [Method] Add -# 29| -1: [TypeMention] Complex -#-----| 2: (Parameters) -# 29| 0: [Parameter] left -# 29| -1: [TypeMention] Complex -# 29| 1: [Parameter] right -# 29| -1: [TypeMention] Complex -# 30| 4: [ObjectCreation] object creation of type Complex -# 30| -2: [TypeMention] Complex -# 30| -1: [ObjectInitializer] { ..., ... } -# 30| 0: [MemberInitializer] ... = ... -# 30| 0: [PropertyCall] access to property Real -# 30| 1: [AddExpr] ... + ... -# 30| 0: [PropertyCall] access to property Real -# 30| -1: [ParameterAccess] access to parameter left -# 30| 1: [PropertyCall] access to property Real -# 30| -1: [ParameterAccess] access to parameter right -# 30| 1: [MemberInitializer] ... = ... -# 30| 0: [PropertyCall] access to property Imaginary -# 30| 1: [AddExpr] ... + ... -# 30| 0: [PropertyCall] access to property Imaginary -# 30| -1: [ParameterAccess] access to parameter left -# 30| 1: [PropertyCall] access to property Imaginary -# 30| -1: [ParameterAccess] access to parameter right -# 32| 11: [Method] Subtract -# 32| -1: [TypeMention] Complex -#-----| 2: (Parameters) -# 32| 0: [Parameter] left -# 32| -1: [TypeMention] Complex -# 32| 1: [Parameter] right -# 32| -1: [TypeMention] Complex +# 29| 0: [Parameter] value +# 31| 6: [InstanceConstructor] Complex +# 31| 4: [BlockStmt] {...} +# 33| 7: [Method] Zero +# 33| -1: [TypeMention] Complex # 33| 4: [ObjectCreation] object creation of type Complex -# 33| -2: [TypeMention] Complex -# 33| -1: [ObjectInitializer] { ..., ... } -# 33| 0: [MemberInitializer] ... = ... -# 33| 0: [PropertyCall] access to property Real -# 33| 1: [SubExpr] ... - ... -# 33| 0: [PropertyCall] access to property Real -# 33| -1: [ParameterAccess] access to parameter left -# 33| 1: [PropertyCall] access to property Real -# 33| -1: [ParameterAccess] access to parameter right -# 33| 1: [MemberInitializer] ... = ... -# 33| 0: [PropertyCall] access to property Imaginary -# 33| 1: [SubExpr] ... - ... -# 33| 0: [PropertyCall] access to property Imaginary -# 33| -1: [ParameterAccess] access to parameter left -# 33| 1: [PropertyCall] access to property Imaginary -# 33| -1: [ParameterAccess] access to parameter right +# 33| 0: [TypeMention] Complex +# 35| 8: [IncrementOperator] ++ +# 35| -1: [TypeMention] Complex +#-----| 2: (Parameters) +# 35| 0: [Parameter] other +# 35| -1: [TypeMention] Complex +# 36| 4: [ObjectCreation] object creation of type Complex +# 36| -2: [TypeMention] Complex +# 36| -1: [ObjectInitializer] { ..., ... } +# 36| 0: [MemberInitializer] ... = ... +# 36| 0: [PropertyCall] access to property Real +# 36| 1: [AddExpr] ... + ... +# 36| 0: [PropertyCall] access to property Real +# 36| -1: [ParameterAccess] access to parameter other +# 36| 1: [DoubleLiteral] 1 +# 36| 1: [MemberInitializer] ... = ... +# 36| 0: [PropertyCall] access to property Imaginary +# 36| 1: [PropertyCall] access to property Imaginary +# 36| -1: [ParameterAccess] access to parameter other +# 38| 9: [DecrementOperator] -- +# 38| -1: [TypeMention] Complex +#-----| 2: (Parameters) +# 38| 0: [Parameter] other +# 38| -1: [TypeMention] Complex +# 39| 4: [ObjectCreation] object creation of type Complex +# 39| -2: [TypeMention] Complex +# 39| -1: [ObjectInitializer] { ..., ... } +# 39| 0: [MemberInitializer] ... = ... +# 39| 0: [PropertyCall] access to property Real +# 39| 1: [SubExpr] ... - ... +# 39| 0: [PropertyCall] access to property Real +# 39| -1: [ParameterAccess] access to parameter other +# 39| 1: [DoubleLiteral] 1 +# 39| 1: [MemberInitializer] ... = ... +# 39| 0: [PropertyCall] access to property Imaginary +# 39| 1: [PropertyCall] access to property Imaginary +# 39| -1: [ParameterAccess] access to parameter other +# 41| 10: [AddOperator] + +# 41| -1: [TypeMention] Complex +#-----| 2: (Parameters) +# 41| 0: [Parameter] left +# 41| -1: [TypeMention] Complex +# 41| 1: [Parameter] right +# 41| -1: [TypeMention] Complex +# 42| 4: [ObjectCreation] object creation of type Complex +# 42| -2: [TypeMention] Complex +# 42| -1: [ObjectInitializer] { ..., ... } +# 42| 0: [MemberInitializer] ... = ... +# 42| 0: [PropertyCall] access to property Real +# 42| 1: [AddExpr] ... + ... +# 42| 0: [PropertyCall] access to property Real +# 42| -1: [ParameterAccess] access to parameter left +# 42| 1: [PropertyCall] access to property Real +# 42| -1: [ParameterAccess] access to parameter right +# 42| 1: [MemberInitializer] ... = ... +# 42| 0: [PropertyCall] access to property Imaginary +# 42| 1: [AddExpr] ... + ... +# 42| 0: [PropertyCall] access to property Imaginary +# 42| -1: [ParameterAccess] access to parameter left +# 42| 1: [PropertyCall] access to property Imaginary +# 42| -1: [ParameterAccess] access to parameter right +# 44| 11: [SubOperator] - +# 44| -1: [TypeMention] Complex +#-----| 2: (Parameters) +# 44| 0: [Parameter] left +# 44| -1: [TypeMention] Complex +# 44| 1: [Parameter] right +# 44| -1: [TypeMention] Complex +# 45| 4: [ObjectCreation] object creation of type Complex +# 45| -2: [TypeMention] Complex +# 45| -1: [ObjectInitializer] { ..., ... } +# 45| 0: [MemberInitializer] ... = ... +# 45| 0: [PropertyCall] access to property Real +# 45| 1: [SubExpr] ... - ... +# 45| 0: [PropertyCall] access to property Real +# 45| -1: [ParameterAccess] access to parameter left +# 45| 1: [PropertyCall] access to property Real +# 45| -1: [ParameterAccess] access to parameter right +# 45| 1: [MemberInitializer] ... = ... +# 45| 0: [PropertyCall] access to property Imaginary +# 45| 1: [SubExpr] ... - ... +# 45| 0: [PropertyCall] access to property Imaginary +# 45| -1: [ParameterAccess] access to parameter left +# 45| 1: [PropertyCall] access to property Imaginary +# 45| -1: [ParameterAccess] access to parameter right +# 47| 12: [ExplicitConversionOperator] explicit conversion +# 47| -1: [TypeMention] int +#-----| 2: (Parameters) +# 47| 0: [Parameter] n +# 47| -1: [TypeMention] Complex +# 47| 4: [CastExpr] (...) ... +# 47| 0: [TypeAccess] access to type Int32 +# 47| 0: [TypeMention] int +# 47| 1: [PropertyCall] access to property Real +# 47| -1: [ParameterAccess] access to parameter n +# 49| 13: [ExplicitConversionOperator] explicit conversion +# 49| -1: [TypeMention] short +#-----| 2: (Parameters) +# 49| 0: [Parameter] n +# 49| -1: [TypeMention] Complex +# 49| 4: [CastExpr] (...) ... +# 49| 0: [TypeAccess] access to type Int16 +# 49| 0: [TypeMention] short +# 49| 1: [PropertyCall] access to property Real +# 49| -1: [ParameterAccess] access to parameter n +# 51| 14: [Method] Inc +# 51| -1: [TypeMention] Complex +# 51| -1: [TypeMention] INumber +# 51| 1: [TypeMention] Complex +#-----| 2: (Parameters) +# 51| 0: [Parameter] other +# 51| -1: [TypeMention] Complex +# 52| 4: [ObjectCreation] object creation of type Complex +# 52| -2: [TypeMention] Complex +# 52| -1: [ObjectInitializer] { ..., ... } +# 52| 0: [MemberInitializer] ... = ... +# 52| 0: [PropertyCall] access to property Real +# 52| 1: [AddExpr] ... + ... +# 52| 0: [PropertyCall] access to property Real +# 52| -1: [ParameterAccess] access to parameter other +# 52| 1: [DoubleLiteral] 1 +# 52| 1: [MemberInitializer] ... = ... +# 52| 0: [PropertyCall] access to property Imaginary +# 52| 1: [PropertyCall] access to property Imaginary +# 52| -1: [ParameterAccess] access to parameter other +# 54| 15: [Method] Dec +# 54| -1: [TypeMention] Complex +# 54| -1: [TypeMention] INumber +# 54| 1: [TypeMention] Complex +#-----| 2: (Parameters) +# 54| 0: [Parameter] other +# 54| -1: [TypeMention] Complex +# 55| 4: [ObjectCreation] object creation of type Complex +# 55| -2: [TypeMention] Complex +# 55| -1: [ObjectInitializer] { ..., ... } +# 55| 0: [MemberInitializer] ... = ... +# 55| 0: [PropertyCall] access to property Real +# 55| 1: [SubExpr] ... - ... +# 55| 0: [PropertyCall] access to property Real +# 55| -1: [ParameterAccess] access to parameter other +# 55| 1: [DoubleLiteral] 1 +# 55| 1: [MemberInitializer] ... = ... +# 55| 0: [PropertyCall] access to property Imaginary +# 55| 1: [PropertyCall] access to property Imaginary +# 55| -1: [ParameterAccess] access to parameter other +# 57| 16: [Method] Add +# 57| -1: [TypeMention] Complex +#-----| 2: (Parameters) +# 57| 0: [Parameter] left +# 57| -1: [TypeMention] Complex +# 57| 1: [Parameter] right +# 57| -1: [TypeMention] Complex +# 58| 4: [ObjectCreation] object creation of type Complex +# 58| -2: [TypeMention] Complex +# 58| -1: [ObjectInitializer] { ..., ... } +# 58| 0: [MemberInitializer] ... = ... +# 58| 0: [PropertyCall] access to property Real +# 58| 1: [AddExpr] ... + ... +# 58| 0: [PropertyCall] access to property Real +# 58| -1: [ParameterAccess] access to parameter left +# 58| 1: [PropertyCall] access to property Real +# 58| -1: [ParameterAccess] access to parameter right +# 58| 1: [MemberInitializer] ... = ... +# 58| 0: [PropertyCall] access to property Imaginary +# 58| 1: [AddExpr] ... + ... +# 58| 0: [PropertyCall] access to property Imaginary +# 58| -1: [ParameterAccess] access to parameter left +# 58| 1: [PropertyCall] access to property Imaginary +# 58| -1: [ParameterAccess] access to parameter right +# 60| 17: [Method] Subtract +# 60| -1: [TypeMention] Complex +#-----| 2: (Parameters) +# 60| 0: [Parameter] left +# 60| -1: [TypeMention] Complex +# 60| 1: [Parameter] right +# 60| -1: [TypeMention] Complex +# 61| 4: [ObjectCreation] object creation of type Complex +# 61| -2: [TypeMention] Complex +# 61| -1: [ObjectInitializer] { ..., ... } +# 61| 0: [MemberInitializer] ... = ... +# 61| 0: [PropertyCall] access to property Real +# 61| 1: [SubExpr] ... - ... +# 61| 0: [PropertyCall] access to property Real +# 61| -1: [ParameterAccess] access to parameter left +# 61| 1: [PropertyCall] access to property Real +# 61| -1: [ParameterAccess] access to parameter right +# 61| 1: [MemberInitializer] ... = ... +# 61| 0: [PropertyCall] access to property Imaginary +# 61| 1: [SubExpr] ... - ... +# 61| 0: [PropertyCall] access to property Imaginary +# 61| -1: [ParameterAccess] access to parameter left +# 61| 1: [PropertyCall] access to property Imaginary +# 61| -1: [ParameterAccess] access to parameter right Strings.cs: # 3| [Class] MyTestClass # 5| 5: [Method] M1 diff --git a/csharp/ql/test/library-tests/csharp11/StaticInterfaceMembers.cs b/csharp/ql/test/library-tests/csharp11/StaticInterfaceMembers.cs index 6be0ddc54b1..b6835dd57a4 100644 --- a/csharp/ql/test/library-tests/csharp11/StaticInterfaceMembers.cs +++ b/csharp/ql/test/library-tests/csharp11/StaticInterfaceMembers.cs @@ -4,6 +4,18 @@ public interface INumber where T : INumber static virtual T operator --(T other) => other; + static abstract T operator +(T left, T right); + + static virtual T operator -(T left, T right) => left; + + static abstract explicit operator int(T n); + + static abstract explicit operator short(T n); + + static abstract T Inc(T other); + + static virtual T Dec(T other) => other; + static abstract T Add(T left, T right); static virtual T Subtract(T left, T right) => left; @@ -26,6 +38,22 @@ public class Complex : INumber public static Complex operator --(Complex other) => new Complex { Real = other.Real - 1.0, Imaginary = other.Imaginary }; + static Complex INumber.operator +(Complex left, Complex right) => + new Complex { Real = left.Real + right.Real, Imaginary = left.Imaginary + right.Imaginary }; + + static Complex INumber.operator -(Complex left, Complex right) => + new Complex { Real = left.Real - right.Real, Imaginary = left.Imaginary - right.Imaginary }; + + public static explicit operator int(Complex n) => (int)n.Real; + + static explicit INumber.operator short(Complex n) => (short)n.Real; + + static Complex INumber.Inc(Complex other) => + new Complex { Real = other.Real + 1.0, Imaginary = other.Imaginary }; + + static Complex INumber.Dec(Complex other) => + new Complex { Real = other.Real - 1.0, Imaginary = other.Imaginary }; + public static Complex Add(Complex left, Complex right) => new Complex { Real = left.Real + right.Real, Imaginary = left.Imaginary + right.Imaginary }; diff --git a/csharp/ql/test/library-tests/csharp11/fileScoped.expected b/csharp/ql/test/library-tests/csharp11/fileScoped.expected new file mode 100644 index 00000000000..18c91b36fd2 --- /dev/null +++ b/csharp/ql/test/library-tests/csharp11/fileScoped.expected @@ -0,0 +1,116 @@ +typemodifiers +| FileScoped1.cs:1:16:1:17 | I1 | file | +| FileScoped1.cs:3:16:3:17 | I2 | file | +| FileScoped1.cs:5:12:5:13 | C1 | file | +| FileScoped1.cs:7:14:7:15 | C2 | public | +| FileScoped1.cs:9:14:9:15 | C3 | public | +| FileScoped1.cs:11:16:11:17 | IC | file | +| FileScoped1.cs:13:12:13:16 | C4<> | file | +| FileScoped1.cs:13:12:13:16 | C4 | file | +| FileScoped1.cs:15:12:15:16 | C5<> | file | +| FileScoped1.cs:17:13:17:14 | S1 | file | +| FileScoped1.cs:17:13:17:14 | S1 | sealed | +| FileScoped1.cs:19:11:19:12 | E1 | file | +| FileScoped1.cs:19:11:19:12 | E1 | sealed | +| FileScoped1.cs:21:20:21:21 | D1 | file | +| FileScoped1.cs:21:20:21:21 | D1 | sealed | +| FileScoped1.cs:23:1:23:18 | R1 | file | +| FileScoped1.cs:23:1:23:18 | R1 | record | +| FileScoped1.cs:25:1:25:26 | RS1 | file | +| FileScoped1.cs:25:1:25:26 | RS1 | record | +| FileScoped1.cs:25:1:25:26 | RS1 | sealed | +| FileScoped2.cs:1:16:1:17 | I1 | file | +| FileScoped2.cs:3:18:3:19 | I2 | public | +| FileScoped2.cs:5:12:5:13 | C1 | file | +| FileScoped2.cs:7:12:7:13 | C2 | file | +| FileScoped2.cs:9:12:9:13 | IC | file | +| FileScoped2.cs:11:12:11:16 | C4<> | file | +| FileScoped2.cs:11:12:11:16 | C4 | file | +| FileScoped2.cs:13:12:13:16 | C5<> | file | +| FileScoped2.cs:15:13:15:14 | S1 | file | +| FileScoped2.cs:15:13:15:14 | S1 | sealed | +| FileScoped2.cs:17:11:17:12 | E1 | file | +| FileScoped2.cs:17:11:17:12 | E1 | sealed | +| FileScoped2.cs:19:20:19:21 | D1 | file | +| FileScoped2.cs:19:20:19:21 | D1 | sealed | +| FileScoped2.cs:21:1:21:18 | R1 | file | +| FileScoped2.cs:21:1:21:18 | R1 | record | +| FileScoped2.cs:23:1:23:26 | RS1 | file | +| FileScoped2.cs:23:1:23:26 | RS1 | record | +| FileScoped2.cs:23:1:23:26 | RS1 | sealed | +| FileScoped3.cs:3:16:3:18 | I10 | file | +| FileScoped3.cs:5:12:5:14 | C10 | file | +| FileScoped3.cs:7:14:7:16 | C11 | public | +| FileScoped4.cs:3:18:3:20 | I10 | public | +| FileScoped4.cs:5:12:5:14 | C10 | file | +| FileScoped4.cs:7:12:7:14 | C11 | file | +qualifiedtypes +| FileScoped1.cs:1:16:1:17 | I1 | I1 | +| FileScoped1.cs:3:16:3:17 | I2 | I2 | +| FileScoped1.cs:5:12:5:13 | C1 | C1 | +| FileScoped1.cs:7:14:7:15 | C2 | C2 | +| FileScoped1.cs:9:14:9:15 | C3 | C3 | +| FileScoped1.cs:11:16:11:17 | IC | IC | +| FileScoped1.cs:13:12:13:16 | C4<> | C4<> | +| FileScoped1.cs:13:12:13:16 | C4 | C4 | +| FileScoped1.cs:15:12:15:16 | C5<> | C5<> | +| FileScoped1.cs:17:13:17:14 | S1 | S1 | +| FileScoped1.cs:19:11:19:12 | E1 | E1 | +| FileScoped1.cs:21:20:21:21 | D1 | D1 | +| FileScoped1.cs:23:1:23:18 | R1 | R1 | +| FileScoped1.cs:25:1:25:26 | RS1 | RS1 | +| FileScoped2.cs:1:16:1:17 | I1 | I1 | +| FileScoped2.cs:3:18:3:19 | I2 | I2 | +| FileScoped2.cs:5:12:5:13 | C1 | C1 | +| FileScoped2.cs:7:12:7:13 | C2 | C2 | +| FileScoped2.cs:9:12:9:13 | IC | IC | +| FileScoped2.cs:11:12:11:16 | C4<> | C4<> | +| FileScoped2.cs:11:12:11:16 | C4 | C4 | +| FileScoped2.cs:13:12:13:16 | C5<> | C5<> | +| FileScoped2.cs:15:13:15:14 | S1 | S1 | +| FileScoped2.cs:17:11:17:12 | E1 | E1 | +| FileScoped2.cs:19:20:19:21 | D1 | D1 | +| FileScoped2.cs:21:1:21:18 | R1 | R1 | +| FileScoped2.cs:23:1:23:26 | RS1 | RS1 | +| FileScoped3.cs:3:16:3:18 | I10 | TestFileScoped.I10 | +| FileScoped3.cs:5:12:5:14 | C10 | TestFileScoped.C10 | +| FileScoped3.cs:7:14:7:16 | C11 | TestFileScoped.C11 | +| FileScoped4.cs:3:18:3:20 | I10 | TestFileScoped.I10 | +| FileScoped4.cs:5:12:5:14 | C10 | TestFileScoped.C10 | +| FileScoped4.cs:7:12:7:14 | C11 | TestFileScoped.C11 | +filetypes +| FileScoped1.cs:1:16:1:17 | I1 | +| FileScoped1.cs:3:16:3:17 | I2 | +| FileScoped1.cs:5:12:5:13 | C1 | +| FileScoped1.cs:11:16:11:17 | IC | +| FileScoped1.cs:13:12:13:16 | C4<> | +| FileScoped1.cs:13:12:13:16 | C4 | +| FileScoped1.cs:15:12:15:16 | C5<> | +| FileScoped1.cs:17:13:17:14 | S1 | +| FileScoped1.cs:19:11:19:12 | E1 | +| FileScoped1.cs:21:20:21:21 | D1 | +| FileScoped1.cs:23:1:23:18 | R1 | +| FileScoped1.cs:25:1:25:26 | RS1 | +| FileScoped2.cs:1:16:1:17 | I1 | +| FileScoped2.cs:5:12:5:13 | C1 | +| FileScoped2.cs:7:12:7:13 | C2 | +| FileScoped2.cs:9:12:9:13 | IC | +| FileScoped2.cs:11:12:11:16 | C4<> | +| FileScoped2.cs:11:12:11:16 | C4 | +| FileScoped2.cs:13:12:13:16 | C5<> | +| FileScoped2.cs:15:13:15:14 | S1 | +| FileScoped2.cs:17:11:17:12 | E1 | +| FileScoped2.cs:19:20:19:21 | D1 | +| FileScoped2.cs:21:1:21:18 | R1 | +| FileScoped2.cs:23:1:23:26 | RS1 | +| FileScoped3.cs:3:16:3:18 | I10 | +| FileScoped3.cs:5:12:5:14 | C10 | +| FileScoped4.cs:5:12:5:14 | C10 | +| FileScoped4.cs:7:12:7:14 | C11 | +internaltypes +publictypes +| FileScoped1.cs:7:14:7:15 | C2 | +| FileScoped1.cs:9:14:9:15 | C3 | +| FileScoped2.cs:3:18:3:19 | I2 | +| FileScoped3.cs:7:14:7:16 | C11 | +| FileScoped4.cs:3:18:3:20 | I10 | diff --git a/csharp/ql/test/library-tests/csharp11/fileScoped.ql b/csharp/ql/test/library-tests/csharp11/fileScoped.ql new file mode 100644 index 00000000000..6e837efa0c5 --- /dev/null +++ b/csharp/ql/test/library-tests/csharp11/fileScoped.ql @@ -0,0 +1,42 @@ +import csharp +private import semmle.code.csharp.commons.QualifiedName + +private predicate isInteresting(Type t) { + ( + t instanceof Class or + t instanceof Interface or + t instanceof Struct or + t instanceof Enum or + t instanceof DelegateType or + t instanceof RecordType + ) and + t.getFile().getStem().matches("FileScoped%") +} + +query predicate typemodifiers(Type t, string modifier) { + isInteresting(t) and + t.(Modifiable).hasModifier(modifier) +} + +query predicate qualifiedtypes(Type t, string qualifiedName) { + isInteresting(t) and + exists(string qualifier, string name | + t.hasQualifiedName(qualifier, name) and + qualifiedName = getQualifiedName(qualifier, name) + ) +} + +query predicate filetypes(Type t) { + isInteresting(t) and + t.isFile() +} + +query predicate internaltypes(Type t) { + isInteresting(t) and + t.isInternal() +} + +query predicate publictypes(Type t) { + isInteresting(t) and + t.isPublic() +} diff --git a/csharp/ql/test/library-tests/csharp11/staticInterfaceMembers.expected b/csharp/ql/test/library-tests/csharp11/staticInterfaceMembers.expected index c5068de6372..5de61507424 100644 --- a/csharp/ql/test/library-tests/csharp11/staticInterfaceMembers.expected +++ b/csharp/ql/test/library-tests/csharp11/staticInterfaceMembers.expected @@ -5,16 +5,55 @@ interfacemembers | INumber<> | StaticInterfaceMembers.cs:5:31:5:32 | -- | public | | INumber<> | StaticInterfaceMembers.cs:5:31:5:32 | -- | static | | INumber<> | StaticInterfaceMembers.cs:5:31:5:32 | -- | virtual | -| INumber<> | StaticInterfaceMembers.cs:7:23:7:25 | Add | abstract | -| INumber<> | StaticInterfaceMembers.cs:7:23:7:25 | Add | public | -| INumber<> | StaticInterfaceMembers.cs:7:23:7:25 | Add | static | -| INumber<> | StaticInterfaceMembers.cs:9:22:9:29 | Subtract | public | -| INumber<> | StaticInterfaceMembers.cs:9:22:9:29 | Subtract | static | -| INumber<> | StaticInterfaceMembers.cs:9:22:9:29 | Subtract | virtual | -| INumber<> | StaticInterfaceMembers.cs:11:14:11:17 | Zero | public | -| INumber<> | StaticInterfaceMembers.cs:11:14:11:17 | Zero | static | +| INumber<> | StaticInterfaceMembers.cs:7:32:7:32 | + | abstract | +| INumber<> | StaticInterfaceMembers.cs:7:32:7:32 | + | public | +| INumber<> | StaticInterfaceMembers.cs:7:32:7:32 | + | static | +| INumber<> | StaticInterfaceMembers.cs:9:31:9:31 | - | public | +| INumber<> | StaticInterfaceMembers.cs:9:31:9:31 | - | static | +| INumber<> | StaticInterfaceMembers.cs:9:31:9:31 | - | virtual | +| INumber<> | StaticInterfaceMembers.cs:11:30:11:37 | explicit conversion | abstract | +| INumber<> | StaticInterfaceMembers.cs:11:30:11:37 | explicit conversion | public | +| INumber<> | StaticInterfaceMembers.cs:11:30:11:37 | explicit conversion | static | +| INumber<> | StaticInterfaceMembers.cs:13:30:13:37 | explicit conversion | abstract | +| INumber<> | StaticInterfaceMembers.cs:13:30:13:37 | explicit conversion | public | +| INumber<> | StaticInterfaceMembers.cs:13:30:13:37 | explicit conversion | static | +| INumber<> | StaticInterfaceMembers.cs:15:23:15:25 | Inc | abstract | +| INumber<> | StaticInterfaceMembers.cs:15:23:15:25 | Inc | public | +| INumber<> | StaticInterfaceMembers.cs:15:23:15:25 | Inc | static | +| INumber<> | StaticInterfaceMembers.cs:17:22:17:24 | Dec | public | +| INumber<> | StaticInterfaceMembers.cs:17:22:17:24 | Dec | static | +| INumber<> | StaticInterfaceMembers.cs:17:22:17:24 | Dec | virtual | +| INumber<> | StaticInterfaceMembers.cs:19:23:19:25 | Add | abstract | +| INumber<> | StaticInterfaceMembers.cs:19:23:19:25 | Add | public | +| INumber<> | StaticInterfaceMembers.cs:19:23:19:25 | Add | static | +| INumber<> | StaticInterfaceMembers.cs:21:22:21:29 | Subtract | public | +| INumber<> | StaticInterfaceMembers.cs:21:22:21:29 | Subtract | static | +| INumber<> | StaticInterfaceMembers.cs:21:22:21:29 | Subtract | virtual | +| INumber<> | StaticInterfaceMembers.cs:23:14:23:17 | Zero | public | +| INumber<> | StaticInterfaceMembers.cs:23:14:23:17 | Zero | static | implements -| StaticInterfaceMembers.cs:23:36:23:37 | ++ | StaticInterfaceMembers.cs:3:32:3:33 | ++ | -| StaticInterfaceMembers.cs:26:36:26:37 | -- | StaticInterfaceMembers.cs:5:31:5:32 | -- | -| StaticInterfaceMembers.cs:29:27:29:29 | Add | StaticInterfaceMembers.cs:7:23:7:25 | Add | -| StaticInterfaceMembers.cs:32:27:32:34 | Subtract | StaticInterfaceMembers.cs:9:22:9:29 | Subtract | +| StaticInterfaceMembers.cs:35:36:35:37 | ++ | StaticInterfaceMembers.cs:3:32:3:33 | ++ | +| StaticInterfaceMembers.cs:38:36:38:37 | -- | StaticInterfaceMembers.cs:5:31:5:32 | -- | +| StaticInterfaceMembers.cs:41:46:41:46 | + | StaticInterfaceMembers.cs:7:32:7:32 | + | +| StaticInterfaceMembers.cs:44:46:44:46 | - | StaticInterfaceMembers.cs:9:31:9:31 | - | +| StaticInterfaceMembers.cs:47:28:47:35 | explicit conversion | StaticInterfaceMembers.cs:11:30:11:37 | explicit conversion | +| StaticInterfaceMembers.cs:49:38:49:45 | explicit conversion | StaticInterfaceMembers.cs:13:30:13:37 | explicit conversion | +| StaticInterfaceMembers.cs:51:37:51:39 | Inc | StaticInterfaceMembers.cs:15:23:15:25 | Inc | +| StaticInterfaceMembers.cs:54:37:54:39 | Dec | StaticInterfaceMembers.cs:17:22:17:24 | Dec | +| StaticInterfaceMembers.cs:57:27:57:29 | Add | StaticInterfaceMembers.cs:19:23:19:25 | Add | +| StaticInterfaceMembers.cs:60:27:60:34 | Subtract | StaticInterfaceMembers.cs:21:22:21:29 | Subtract | +publicmembers +| StaticInterfaceMembers.cs:28:19:28:22 | Real | +| StaticInterfaceMembers.cs:29:19:29:27 | Imaginary | +| StaticInterfaceMembers.cs:31:12:31:18 | Complex | +| StaticInterfaceMembers.cs:33:27:33:30 | Zero | +| StaticInterfaceMembers.cs:35:36:35:37 | ++ | +| StaticInterfaceMembers.cs:38:36:38:37 | -- | +| StaticInterfaceMembers.cs:41:46:41:46 | + | +| StaticInterfaceMembers.cs:44:46:44:46 | - | +| StaticInterfaceMembers.cs:47:28:47:35 | explicit conversion | +| StaticInterfaceMembers.cs:49:38:49:45 | explicit conversion | +| StaticInterfaceMembers.cs:51:37:51:39 | Inc | +| StaticInterfaceMembers.cs:54:37:54:39 | Dec | +| StaticInterfaceMembers.cs:57:27:57:29 | Add | +| StaticInterfaceMembers.cs:60:27:60:34 | Subtract | diff --git a/csharp/ql/test/library-tests/csharp11/staticInterfaceMembers.ql b/csharp/ql/test/library-tests/csharp11/staticInterfaceMembers.ql index 0d92606e4dd..11e1b695020 100644 --- a/csharp/ql/test/library-tests/csharp11/staticInterfaceMembers.ql +++ b/csharp/ql/test/library-tests/csharp11/staticInterfaceMembers.ql @@ -16,3 +16,9 @@ query predicate implements(Overridable o, Virtualizable v) { v.isStatic() and v.getAnImplementor() = o } + +query predicate publicmembers(Member m) { + m.getFile().getStem() = "StaticInterfaceMembers" and + m.getDeclaringType().getName() = "Complex" and + m.isPublic() +} diff --git a/csharp/ql/test/query-tests/Likely Bugs/StaticFieldWrittenByInstance/StaticFieldWrittenByInstance.cs b/csharp/ql/test/query-tests/Likely Bugs/StaticFieldWrittenByInstance/StaticFieldWrittenByInstance.cs index d26c5af5df2..244a6b2fd15 100644 --- a/csharp/ql/test/query-tests/Likely Bugs/StaticFieldWrittenByInstance/StaticFieldWrittenByInstance.cs +++ b/csharp/ql/test/query-tests/Likely Bugs/StaticFieldWrittenByInstance/StaticFieldWrittenByInstance.cs @@ -26,4 +26,21 @@ class StaticFields staticField = 0; // BAD instanceField = 0; // OK } + + static object backingField; + static object StaticProp + { + get + { + return backingField ?? (backingField = new object()); // OK + } + } + + object Prop + { + get + { + return backingField ?? (backingField = new object()); // BAD + } + } } diff --git a/csharp/ql/test/query-tests/Likely Bugs/StaticFieldWrittenByInstance/StaticFieldWrittenByInstance.expected b/csharp/ql/test/query-tests/Likely Bugs/StaticFieldWrittenByInstance/StaticFieldWrittenByInstance.expected index c853d744f85..5311ab424ac 100644 --- a/csharp/ql/test/query-tests/Likely Bugs/StaticFieldWrittenByInstance/StaticFieldWrittenByInstance.expected +++ b/csharp/ql/test/query-tests/Likely Bugs/StaticFieldWrittenByInstance/StaticFieldWrittenByInstance.expected @@ -1,2 +1,3 @@ -| StaticFieldWrittenByInstance.cs:15:9:15:19 | access to field staticField | Write to static field from instance method or constructor. | -| StaticFieldWrittenByInstance.cs:26:9:26:19 | access to field staticField | Write to static field from instance method or constructor. | +| StaticFieldWrittenByInstance.cs:15:9:15:19 | access to field staticField | Write to static field from instance method, property, or constructor. | +| StaticFieldWrittenByInstance.cs:26:9:26:19 | access to field staticField | Write to static field from instance method, property, or constructor. | +| StaticFieldWrittenByInstance.cs:43:37:43:48 | access to field backingField | Write to static field from instance method, property, or constructor. | diff --git a/docs/codeql/codeql-for-visual-studio-code/about-codeql-for-visual-studio-code.rst b/docs/codeql/codeql-for-visual-studio-code/about-codeql-for-visual-studio-code.rst index 02678c27f84..72e2756ea5c 100644 --- a/docs/codeql/codeql-for-visual-studio-code/about-codeql-for-visual-studio-code.rst +++ b/docs/codeql/codeql-for-visual-studio-code/about-codeql-for-visual-studio-code.rst @@ -14,7 +14,7 @@ CodeQL for Visual Studio Code provides an easy way to run queries from the large With these queries, or your own custom queries, you can analyze databases generated from source code to find errors and security vulnerabilities. The Results view shows the flow of data through the results of path queries, which is essential for triaging security results. -The CodeQL extension also adds a **CodeQL** sidebar view to VS Code. This contains a list of databases, and an overview of the queries that you have run in the current session. +The CodeQL extension also adds a **CodeQL** sidebar view to VS Code. This contains a list of local CodeQL databases, an overview of the queries that you have run in the current session, and a variant analysis view for large scale analysis. The extension provides standard `IntelliSense `__ features for query files (extension ``.ql``) and library files (extension ``.qll``) that you open in the Visual Studio Code editor. @@ -36,4 +36,5 @@ Further reading ------------------- - ":doc:`Setting up CodeQL in Visual Studio Code `" -- ":doc:`Analyzing your projects `" \ No newline at end of file +- ":doc:`Analyzing your projects `" +- ":doc:`Running CodeQL queries at scale with multi-repository variant analysis `" diff --git a/docs/codeql/codeql-for-visual-studio-code/analyzing-your-projects.rst b/docs/codeql/codeql-for-visual-studio-code/analyzing-your-projects.rst index 7cf8907568d..c3d1f15be6b 100644 --- a/docs/codeql/codeql-for-visual-studio-code/analyzing-your-projects.rst +++ b/docs/codeql/codeql-for-visual-studio-code/analyzing-your-projects.rst @@ -5,7 +5,7 @@ Analyzing your projects ================================================= -You can run queries on CodeQL databases and view the results in Visual Studio Code. +You can run queries on CodeQL databases and view the results in Visual Studio Code. This article explains how to get a CodeQL database and analyze it on your local machine. For information on running analysis at scale across many CodeQL databases, see ":ref:`Running CodeQL queries at scale with multi-repository variant analysis `." Choosing a database ------------------------ @@ -24,8 +24,8 @@ To analyze a project, you need to add a :ref:`CodeQL database ` #. Once you've chosen a database, it is displayed in the Databases view. To see the menu options for interacting with a database, right-click an entry in the list. You can select multiple databases using **Ctrl/Cmd+click**. -Obtaining a local database -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Importing a local database +~~~~~~~~~~~~~~~~~~~~~~~~~~ If you have a CodeQL database saved locally, as an unarchived folder or as a ZIP file, you can add it to Visual Studio Code. There are several ways to obtain a local CodeQL database. @@ -37,6 +37,9 @@ If you have a CodeQL database saved locally, as an unarchived folder or as a ZIP For more information about running query tests, see "`Testing custom queries `__" in the CodeQL CLI help. +Downloading a database from GitHub +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + .. include:: ../reusables/download-github-database.rst Running a query diff --git a/docs/codeql/codeql-for-visual-studio-code/customizing-settings.rst b/docs/codeql/codeql-for-visual-studio-code/customizing-settings.rst index 06802873ae8..fd384255c39 100644 --- a/docs/codeql/codeql-for-visual-studio-code/customizing-settings.rst +++ b/docs/codeql/codeql-for-visual-studio-code/customizing-settings.rst @@ -25,6 +25,8 @@ Editing settings 3. Edit a setting. The new settings are saved automatically. +Alternatively, you can edit the settings in JSON format by opening the command palette and selecting **Preferences: Open User Settings (JSON)**. + Choosing a version of the CodeQL CLI -------------------------------------- @@ -55,8 +57,8 @@ By default, items in the query history view are retained for 30 days. You can se .. _configuring-settings-for-running-queries: -Configuring settings for running queries ------------------------------------------ +Configuring settings for running queries locally +------------------------------------------------ There are a number of settings for **Running Queries**. If your queries run too slowly and time out frequently, you may want to increase the memory. @@ -64,8 +66,49 @@ There are a number of settings for **Running Queries**. If your queries run too To save query server logs in a custom location, edit the **Running Queries: Custom Log Directory** setting. If you use a custom log directory, the extension saves the logs permanently, instead of deleting them automatically after each workspace session. This is useful if you want to investigate these logs to improve the performance of your queries. -Configuring settings for testing queries ------------------------------------------ +Configuring settings for variant analysis +------------------------------------------ + +You can define or edit lists of GitHub repositories for variant analysis, and change to a different controller repository using the **Variant analysis** settings. + +For information on the purpose and requirements for a controller repository, see ":ref:`Setting up a controller repository for variant analysis `." + +You can also edit the items shown in the Variant Analysis Repositories panel by editing a file in your Visual Studio Code workspace called ``databases.json``. This file contains a JSON representation of all the items displayed in the panel. To open your ``databases.json`` file in an editor window, click the **{ }** icon in the top right of the Variant Analysis Repositories panel. You can then see a structured representation of the repos, orgs and lists in your panel. For example: + +.. code-block:: json + + { + "version": 1, + "databases": { + "variantAnalysis": { + "repositoryLists": [ + { + "name": "My favorite JavaScript repos", + "repositories": [ + "facebook/react", + "babel/babel", + "angular/angular" + ] + } + ], + "owners": [ + "microsoft" + ], + "repositories": [ + "apache/hadoop" + ] + } + }, + "selected": { + "kind": "variantAnalysisSystemDefinedList", + "listName": "top_10" + } + } + +You can change the items shown in the panel or add new items by directly editing this file. + +Configuring settings for testing queries locally +------------------------------------------------ To increase the number of threads used for testing queries, you can update the **Running Tests > Number Of Threads** setting. diff --git a/docs/codeql/codeql-for-visual-studio-code/exploring-data-flow-with-path-queries.rst b/docs/codeql/codeql-for-visual-studio-code/exploring-data-flow-with-path-queries.rst index eb7cb84672c..dec599829ed 100644 --- a/docs/codeql/codeql-for-visual-studio-code/exploring-data-flow-with-path-queries.rst +++ b/docs/codeql/codeql-for-visual-studio-code/exploring-data-flow-with-path-queries.rst @@ -3,7 +3,7 @@ .. _exploring-data-flow-with-path-queries: Exploring data flow with path queries -================================================= +===================================== You can run CodeQL queries in VS Code to help you track the flow of data through a program, highlighting areas that are potential security vulnerabilities. @@ -20,8 +20,8 @@ You can also modify the existing queries to model data flow more precisely for t To ensure that your path query uses the correct format and metadata, follow the instructions in ":ref:`Creating path queries `." This topic also contains detailed information about how to define new sources and sinks, as well as templates and examples of how to extend the CodeQL libraries to suit your analysis. -Running path queries in VS Code ------------------------------------ +Running path queries in VS Code locally +--------------------------------------- #. Open a path query in the editor. #. Right-click in the query window and select **CodeQL: Run Query on Selected Database**. (Alternatively, run the command from the Command Palette.) @@ -30,6 +30,8 @@ Running path queries in VS Code #. Click each step to jump to it in the source code and investigate the problem further. #. To navigate the results from your keyboard, you can bind shortcuts to the **CodeQL: Navigate Up/Down/Left/Right in Result Viewer** commands. +When you are ready to run a path query at scale, you can use the Variant Analysis Repositories panel to run the query against up to 1,000 repositories on GitHub.com. For information on running analysis at scale across many CodeQL databases, see ":ref:`Running CodeQL queries at scale with multi-repository variant analysis `." + Further reading ----------------- diff --git a/docs/codeql/codeql-for-visual-studio-code/index.rst b/docs/codeql/codeql-for-visual-studio-code/index.rst index 23fd133794f..1b45ef10a3e 100644 --- a/docs/codeql/codeql-for-visual-studio-code/index.rst +++ b/docs/codeql/codeql-for-visual-studio-code/index.rst @@ -27,6 +27,11 @@ The CodeQL extension for Visual Studio Code adds rich language support for CodeQ VS Code to help you track the flow of data through a program, highlighting areas that are potential security vulnerabilities. +- :doc:`Running CodeQL queries at scale with multi-repository variant analysis + `: You can run queries against groups + of repositories on GitHub.com and view results in Visual Studio Code as each analysis + finishes. + - :doc:`Testing CodeQL queries in Visual Studio Code `: You can run unit tests for CodeQL queries using the Visual Studio Code extension. @@ -40,7 +45,13 @@ The CodeQL extension for Visual Studio Code adds rich language support for CodeQ - :doc:`Troubleshooting CodeQL for Visual Studio Code `: You can use the detailed - information written to the extension's log files if you need to troubleshoot problems. + information written to the extension's log files if you need to troubleshoot problems with + analysis of local CodeQL databases. + +- :doc:`Troubleshooting variant analysis + `: You can use the detailed + information written to workflow log files in your controller repository if you need to + troubleshoot problems with analysis of CodeQL databases stored on GitHub.com. - :doc:`About telemetry in CodeQL for Visual Studio Code `: If you specifically opt in to permit GitHub to do so, GitHub will collect usage data and metrics for the purposes of helping the core developers to improve the CodeQL extension for VS Code. @@ -53,8 +64,10 @@ The CodeQL extension for Visual Studio Code adds rich language support for CodeQ analyzing-your-projects exploring-the-structure-of-your-source-code exploring-data-flow-with-path-queries + running-codeql-queries-at-scale-with-mrva testing-codeql-queries-in-visual-studio-code working-with-codeql-packs-in-visual-studio-code customizing-settings troubleshooting-codeql-for-visual-studio-code + troubleshooting-variant-analysis about-telemetry-in-codeql-for-visual-studio-code diff --git a/docs/codeql/codeql-for-visual-studio-code/running-codeql-queries-at-scale-with-mrva.rst b/docs/codeql/codeql-for-visual-studio-code/running-codeql-queries-at-scale-with-mrva.rst new file mode 100644 index 00000000000..656aec444de --- /dev/null +++ b/docs/codeql/codeql-for-visual-studio-code/running-codeql-queries-at-scale-with-mrva.rst @@ -0,0 +1,163 @@ +:tocdepth: 1 + +.. _running-codeql-queries-at-scale-with-mrva: + +Running CodeQL queries at scale with multi-repository variant analysis +====================================================================== + +.. include:: ../reusables/beta-note-mrva.rst + +About multi-repository variant analysis +--------------------------------------- + +When you write a query to find variants of a security vulnerability and finish testing it locally, the next step is to run it on a large group of repositories. Multi-repository variant analysis (variant analysis) makes it easy run a query on up to 1000 repositories without leaving Visual Studio Code. + +The core functionality of the CodeQL extension helps you write queries and run them locally against a CodeQL database. In contrast, variant analysis allows you to send your CodeQL query to GitHub.com to be tested against a list of repositories. + +When you run variant analysis against a list of repositories, your query is run against each repository that has a CodeQL database available to analyze. GitHub creates and stores the latest CodeQL database for the default branch of thousands of public repositories, including every repository that runs code scanning using CodeQL. + +If you want to run variant analysis on your repositories, you need to enable code scanning using CodeQL on GitHub.com before adding your repository to a list for analysis (either default setup, or advanced setup using the CodeQL action). For information about enabling code scanning using CodeQL, see "`Configuring code scanning automatically `__." + +.. _controller-repository: + +Setting a controller repository for variant analysis +---------------------------------------------------- + +When you run variant analysis, the analysis is run entirely using GitHub Actions. You don't need to create any workflows, but you must specify which GitHub repository the CodeQL extension should use as the "controller repository." Controller repositories can be empty, but they must have at least one commit. The ``GITHUB_TOKEN`` must also have "Read and write permissions" to run workflows in that repository. For more information, see "`Managing GitHub Actions settings for a repository `__." + +.. pull-quote:: + + Note + + - The controller repository visibility can be "public" if you plan to analyze public repositories. The variant analysis will be free. + - The controller repository visibility must be "private" if you need to analyze any private or internal repositories. Any actions minutes used by variant analysis, above the free limit, will be charged to the repository owner. For more information about free minutes and billing, see "`About billing for GitHub Actions `__." + +You must define a controller repository before you can run your first variant analysis. + +.. image:: ../images/codeql-for-visual-studio-code/controller-repository.png + :width: 350 + :alt: Screenshot of the CodeQL extension in Visual Studio Code. The "Variant Analysis Repositories" section is expanded and the "Set up controller repository" button is highlighted with a dark orange outline. + +#. In Visual Studio Code, click **QL** in the left sidebar to display the CodeQL extension. + +#. Expand **Variant Analysis Repositories** and click **Set up controller repository** to display a field for the controller repository. + +#. Type the owner and name of the repository on GitHub.com that you want to use as your controller repository and press the **Enter** key. + +#. If you are prompted to authenticate with GitHub, follow the instructions and sign into your personal or organization account. When you have finished following the process, a prompt from GitHub Authentication may ask for permission to open a URI in Visual Studio Code, click **Open**. + +The name of the controller repository is saved in your settings for the CodeQL extension. For information on how to edit the controller repository, see ":ref:`Customizing settings `." + +Running a query at scale using variant analysis +----------------------------------------------- + +#. Expand the **Variant Analysis Repositories** section, to show the default lists which include a selection of 10, 100, and 1,000 public repositories on GitHub.com for the language that you are analyzing. + +#. Select which GitHub repository or repositories you want to run your query against. Click a row to highlight it, and then click **Select** to select that repository, organization, or list of repositories. If you want to add a new repository, organization, or list, use the options in the header panel. For information, see ":ref:`Creating custom lists of repositories `", later in this article. + + .. image:: ../images/codeql-for-visual-studio-code/variant-analysis-repo-lists.png + :width: 350 + :alt: Screenshot of the CodeQL extension in Visual Studio Code. The "Variant Analysis Repositories" section is expanded. The "Top 10 repositories" item has a checkmark to show that it is currently selected for analysis. The user has clicked on the row for a single repository "octo-org/octo-repo" and it is highlighted blue. The "Select" button for that row is highlighted with a dark orange highlight. + +#. Open the query you want to run, right-click in the query file, and select **CodeQL: Run Variant Analysis** to start variant analysis. + +The CodeQL extension builds a CodeQL pack with your library and any library dependencies. The CodeQL pack and your selected repository list are posted to an API endpoint on GitHub.com which triggers a GitHub Actions dynamic workflow in your controller repository. The workflow spins up multiple parallel jobs to execute the CodeQL query against the repositories in the list, optimizing query execution. As each repository is analyzed, the results are processed and displayed in a Variant Analysis Results view in Visual Studio Code. + +.. pull-quote:: + + Note + + If you need to cancel the variant analysis run for any reason, click **Stop query** in the Variant Analysis Results view. + +Exploring your results +---------------------- + +When you run variant analysis, as soon as a workflow to run your analysis on GitHub is running, a Variant Analysis Results view opens to display the results as they are ready. You can use this view to monitor progress, see any errors, and access the workflow logs in your controller repository. + +.. image:: ../images/codeql-for-visual-studio-code/variant-analysis-results-view.png + :alt: Screenshot of the "Variant Analysis Results" view showing a partially complete run. Analysis of ``angular/angular`` is still running but all other results are displayed. ``facebook/create-react-app`` has three results for this query. + +When your variant analysis run is scheduled, the results view automatically opens. Initially the view shows a list of every repository that was scheduled for analysis. As each repository is analyzed, the view is updated to show a summary of the number of results. To view the detailed results for a repository (including results paths), click the repository name. + +For each repository, you can see: + +- Number of results found by the query +- Visibility of the repository +- Whether analysis is still running (black, moving circle) or finished (green checkmark) +- Number of stars the repository has on GitHub +- When the repository was last updated + +To see the results for a repository: + +.. image:: ../images/codeql-for-visual-studio-code/variant-analysis-result.png + :alt: Screenshot of an example result in the "Variant Analysis Results" view. The result has blue links to the source files in GitHub so you can go straight to the repository to fix the problem. There is also a "Show paths" link because this is a data flow query. + +#. Click the repository name to show a summary of each result. + +#. Explore the information available for each result using links to the source files in GitHub.com and, for data flow queries, the **Show paths** link. For more information, see ":ref:`Exploring data flow with path queries `." + +Exporting your results +---------------------- + +You can export your results for further analysis or to discuss them with collaborators. In the results view, click **Export results** to export the results to a secret gist on GitHub.com or to a markdown file in your workspace. + +.. _custom-lists: + +Creating custom lists of repositories +------------------------------------- + +After you have defined a controller repository, the Variant Analysis Repositories panel shows the lists of repositories that you can select for variant analysis. You can use the options in the panel header to add a specific repository or organization to the panel, and to create and manage custom lists of repositories for variant analysis. + +.. pull-quote:: + + Note + + CodeQL analysis always requires a CodeQL database to run queries against. When you run variant analysis against a list of repositories, your query will only be executed against the repositories that currently have a CodeQL database available to download. The best way to make a repository available for variant analysis is to enable code scanning with CodeQL. For information about enabling code scanning using CodeQL, see "`Configuring code scanning automatically `__." + +Selecting a single GitHub repository or organization for analysis +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +#. In the Variant Analysis Repositories panel, click the **+**, add new database, icon. + +#. From the dropdown menu, click **From a GitHub repository** or **All repositories of GitHub org or owner**. + +#. Type the identifier of the repository or organization that you want to use into the field. + + .. image:: ../images/codeql-for-visual-studio-code/variant-analysis-repo-and-org.png + :width: 350 + :alt: Screenshot of the CodeQL extension in Visual Studio Code. The "Variant Analysis Repositories" section is expanded to show a repository (octo-org/octo-repo) and an organization (octo-org). These items are highlighted with a dark orange outline. + +Creating a custom list of repositories +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +#. In the Variant Analysis Repositories panel, click the |add-list| icon. + +#. Type a name for the new list and press **Enter**. + +#. Select your list in the panel and then click **+**, to add a repository to your list. + +You can manage and edit your custom lists by right-clicking on either the list name, or a repository name within the list, and selecting an option from the context menu. + +The custom lists are stored in your workspace in a ``databases.json`` file. If you want to edit this file directly, you can open it by clicking **{ }** in the panel header. + +For example, if you want to continue analyzing a set of repositories that had results for your query, click **Copy repository list** in the Variant Analysis Results view to add a list of only the repositories that have results to the clipboard as JSON. For example: + +.. code-block:: json + + { + "name": "new-repo-list", + "repositories": [ + "facebook/create-react-app" + ] + } + +You can then insert the ``new-repo-list`` of repositories into your list of custom repository lists for easy access in the Variant Analysis Repositories panel. + +Troubleshooting variant analysis +-------------------------------- + +For information on troubleshooting variant analysis, see +":ref:`Troubleshooting variant analysis `." + +.. |add-list| image:: ../images/codeql-for-visual-studio-code/variant-analysis-add-list.png + :height: 2ex diff --git a/docs/codeql/codeql-for-visual-studio-code/testing-codeql-queries-in-visual-studio-code.rst b/docs/codeql/codeql-for-visual-studio-code/testing-codeql-queries-in-visual-studio-code.rst index edd837a911e..ddc78558496 100644 --- a/docs/codeql/codeql-for-visual-studio-code/testing-codeql-queries-in-visual-studio-code.rst +++ b/docs/codeql/codeql-for-visual-studio-code/testing-codeql-queries-in-visual-studio-code.rst @@ -5,7 +5,7 @@ Testing CodeQL queries in Visual Studio Code ============================================ -You can run unit tests for CodeQL queries using the Visual Studio Code extension. +You can run unit tests for CodeQL queries using the Visual Studio Code extension. When you are sure that your query finds the results you want to identify, you can use variant analysis to run it at scale. For information on running analysis at scale across many CodeQL databases, see ":ref:`Running CodeQL queries at scale with multi-repository variant analysis `." About testing queries in VS Code --------------------------------- diff --git a/docs/codeql/codeql-for-visual-studio-code/troubleshooting-codeql-for-visual-studio-code.rst b/docs/codeql/codeql-for-visual-studio-code/troubleshooting-codeql-for-visual-studio-code.rst index 3f50d29604b..2eecc28f0fb 100644 --- a/docs/codeql/codeql-for-visual-studio-code/troubleshooting-codeql-for-visual-studio-code.rst +++ b/docs/codeql/codeql-for-visual-studio-code/troubleshooting-codeql-for-visual-studio-code.rst @@ -5,7 +5,12 @@ Troubleshooting CodeQL for Visual Studio Code ============================================= -You can use the detailed information written to the extension's log files if you need to troubleshoot problems. +This article explains how to debug problems with the analysis of CodeQL databases that are stored on your local +machine. For information on troubleshooting variant analysis, which runs on GitHub.com, see +":ref:`Troubleshooting variant analysis `." + +You can use the detailed information written to the extension's log files if you need to troubleshoot problems +analyzing CodeQL databases that are stored locally. About the log files -------------------- diff --git a/docs/codeql/codeql-for-visual-studio-code/troubleshooting-variant-analysis.rst b/docs/codeql/codeql-for-visual-studio-code/troubleshooting-variant-analysis.rst new file mode 100644 index 00000000000..193ecbf028b --- /dev/null +++ b/docs/codeql/codeql-for-visual-studio-code/troubleshooting-variant-analysis.rst @@ -0,0 +1,30 @@ +:tocdepth: 1 + +.. _troubleshooting-variant-analysis: + +Troubleshooting variant analysis +================================ + +.. include:: ../reusables/beta-note-mrva.rst + +This article explains how to debug problems with variant analysis, that is, analysis run using GitHub Actions +and not locally on your machine. +For information on troubleshooting local analysis, see +":ref:`Troubleshooting CodeQL for Visual Studio Code `." + +When you run variant analysis, there are two key places where errors and warnings are displayed: + +#. **Visual Studio Code errors** - any problems with creating a CodeQL pack and sending the analysis to GitHub.com are reported as Visual Studio Code errors in the bottom right corner of the application. The problem information is also available in the **Problems** view. +#. **Variant Analysis Results** - any problems with the variant analysis run are reported in this view. + +Variant analysis warning: Problem with controller repository +------------------------------------------------------------ + +If there are problems with the variant analysis run, you will see a warning banner at the top of the Variant Analysis Results tab. For example: + +.. image:: ../images/codeql-for-visual-studio-code/variant-analysis-results-warning.png + :width: 600 + :alt: Screenshot of the "Variant Analysis Results" view showing a warning banner with the text "warning: Problem with controller repository" and "Publicly visible controller repository can't be used to analyze private repositories. 1 private repository was not analyzed." The "Show logs" button is highlighted with a dark orange outline. + +In this example, the user ran variant analysis on a custom list of two repositories. One of the repositories was a private repository and could not be analyzed because they had a public controller repository. Only the public repository was analyzed. To analyze both repositories, this user needs to edit their settings and update the controller repository to a private repository. For information on how to edit the controller repository, see ":ref:`Customizing settings `." + diff --git a/docs/codeql/codeql-language-guides/using-api-graphs-in-python.rst b/docs/codeql/codeql-language-guides/using-api-graphs-in-python.rst index adfcdaa8d60..02adfdaf983 100644 --- a/docs/codeql/codeql-language-guides/using-api-graphs-in-python.rst +++ b/docs/codeql/codeql-language-guides/using-api-graphs-in-python.rst @@ -31,16 +31,16 @@ following snippet demonstrates. This query selects the API graph node corresponding to the ``re`` module. This node represents the fact that the ``re`` module has been imported rather than a specific location in the program where the import happens. Therefore, there will be at most one result per project, and it will not have a useful location, so you'll have to click `Show 1 non-source result` in order to see it. -To find where the ``re`` module is referenced in the program, you can use the ``getAUse`` method. The following query selects all references to the ``re`` module in the current database. +To find where the ``re`` module is referenced in the program, you can use the ``getAValueReachableFromSource`` method. The following query selects all references to the ``re`` module in the current database. .. code-block:: ql import python import semmle.python.ApiGraphs - select API::moduleImport("re").getAUse() + select API::moduleImport("re").getAValueReachableFromSource() -Note that the ``getAUse`` method accounts for local flow, so that ``my_re_compile`` +Note that the ``getAValueReachableFromSource`` method accounts for local flow, so that ``my_re_compile`` in the following snippet is correctly recognized as a reference to the ``re.compile`` function. @@ -53,7 +53,7 @@ correctly recognized as a reference to the ``re.compile`` function. r = my_re_compile(".*") If you only require immediate uses, without taking local flow into account, then you can use -the ``getAnImmediateUse`` method instead. +the ``asSource`` method instead. Note that the given module name *must not* contain any dots. Thus, something like ``API::moduleImport("flask.views")`` will not do what you expect. Instead, this should be decomposed @@ -71,7 +71,7 @@ the above ``re.compile`` example, you can now find references to ``re.compile``. import python import semmle.python.ApiGraphs - select API::moduleImport("re").getMember("compile").getAUse() + select API::moduleImport("re").getMember("compile").getAValueReachableFromSource() In addition to ``getMember``, you can use the ``getUnknownMember`` method to find references to API components where the name is not known statically. You can use the ``getAMember`` method to @@ -89,12 +89,36 @@ where the return value of ``re.compile`` is used: import python import semmle.python.ApiGraphs - select API::moduleImport("re").getMember("compile").getReturn().getAUse() + select API::moduleImport("re").getMember("compile").getReturn().getAValueReachableFromSource() Note that this includes all uses of the result of ``re.compile``, including those reachable via -local flow. To get just the *calls* to ``re.compile``, you can use ``getAnImmediateUse`` instead of -``getAUse``. As this is a common occurrence, you can use ``getACall`` instead of -``getReturn`` followed by ``getAnImmediateUse``. +local flow. To get just the *calls* to ``re.compile``, you can use ``asSource`` instead of +``getAValueReachableFromSource``. As this is a common occurrence, you can, instead of +``getReturn`` followed by ``asSource``, simply use ``getACall``. This will result in an +``API::CallNode``, which deserves a small description of its own. + +``API::CallNode``s are not ``API::Node``s. Instead they are ``DataFlow::Node``s with some convenience +predicates that allows you to recover ``API::Node``s for the return value as well as for arguments +to the call. This enables you to constrain the call in various ways using the API graph. The following +snippet finds all calls to ``re.compile`` where the ``pattern`` argument comes from parsing a command +line argument using the ``argparse`` library. + +.. code-block:: ql + + import python + import semmle.python.ApiGraphs + + from API::CallNode call + where + call = API::moduleImport("re").getMember("compile").getACall() and + call.getParameter(0, "pattern") = + API::moduleImport("argparse") + .getMember("ArgumentParser") + .getReturn() + .getMember("parse_args") + .getMember(_) + select call + Note that the API graph does not distinguish between class instantiations and function calls. As far as it's concerned, both are simply places where an API graph node is called. @@ -122,7 +146,7 @@ all subclasses of ``View``, you must explicitly include the subclasses of ``Meth API::moduleImport("flask").getMember("views").getMember(["View", "MethodView"]).getASubclass*() } - select viewClass().getAUse() + select viewClass().getAValueReachableFromSource() Note the use of the set literal ``["View", "MethodView"]`` to match both classes simultaneously. diff --git a/docs/codeql/images/codeql-for-visual-studio-code/controller-repository.png b/docs/codeql/images/codeql-for-visual-studio-code/controller-repository.png new file mode 100644 index 00000000000..3b4ae62c915 Binary files /dev/null and b/docs/codeql/images/codeql-for-visual-studio-code/controller-repository.png differ diff --git a/docs/codeql/images/codeql-for-visual-studio-code/variant-analysis-add-list.png b/docs/codeql/images/codeql-for-visual-studio-code/variant-analysis-add-list.png new file mode 100644 index 00000000000..53eaac3175e Binary files /dev/null and b/docs/codeql/images/codeql-for-visual-studio-code/variant-analysis-add-list.png differ diff --git a/docs/codeql/images/codeql-for-visual-studio-code/variant-analysis-cli-error.png b/docs/codeql/images/codeql-for-visual-studio-code/variant-analysis-cli-error.png new file mode 100644 index 00000000000..923eff91ddb Binary files /dev/null and b/docs/codeql/images/codeql-for-visual-studio-code/variant-analysis-cli-error.png differ diff --git a/docs/codeql/images/codeql-for-visual-studio-code/variant-analysis-repo-and-org.png b/docs/codeql/images/codeql-for-visual-studio-code/variant-analysis-repo-and-org.png new file mode 100644 index 00000000000..a4dc74ee89b Binary files /dev/null and b/docs/codeql/images/codeql-for-visual-studio-code/variant-analysis-repo-and-org.png differ diff --git a/docs/codeql/images/codeql-for-visual-studio-code/variant-analysis-repo-lists.png b/docs/codeql/images/codeql-for-visual-studio-code/variant-analysis-repo-lists.png new file mode 100644 index 00000000000..b9472c6e19a Binary files /dev/null and b/docs/codeql/images/codeql-for-visual-studio-code/variant-analysis-repo-lists.png differ diff --git a/docs/codeql/images/codeql-for-visual-studio-code/variant-analysis-result.png b/docs/codeql/images/codeql-for-visual-studio-code/variant-analysis-result.png new file mode 100644 index 00000000000..da41f4c8219 Binary files /dev/null and b/docs/codeql/images/codeql-for-visual-studio-code/variant-analysis-result.png differ diff --git a/docs/codeql/images/codeql-for-visual-studio-code/variant-analysis-results-view.png b/docs/codeql/images/codeql-for-visual-studio-code/variant-analysis-results-view.png new file mode 100644 index 00000000000..42fc75b70c5 Binary files /dev/null and b/docs/codeql/images/codeql-for-visual-studio-code/variant-analysis-results-view.png differ diff --git a/docs/codeql/images/codeql-for-visual-studio-code/variant-analysis-results-warning.png b/docs/codeql/images/codeql-for-visual-studio-code/variant-analysis-results-warning.png new file mode 100644 index 00000000000..8fbe9ee7a47 Binary files /dev/null and b/docs/codeql/images/codeql-for-visual-studio-code/variant-analysis-results-warning.png differ diff --git a/docs/codeql/reusables/beta-note-mrva.rst b/docs/codeql/reusables/beta-note-mrva.rst new file mode 100644 index 00000000000..7bcc978797c --- /dev/null +++ b/docs/codeql/reusables/beta-note-mrva.rst @@ -0,0 +1,7 @@ +.. pull-quote:: + + Note + + Multi-repository variant analysis is currently available as a beta release and is subject to change. To use this feature, you must upgrade the CodeQL extension for Visual Studio Code to a minimum of version 1.8.0. + + You can report your feedback in the community discussion for the beta release: https://gh.io/mrva-public-beta-discussion. diff --git a/go/ql/lib/change-notes/2023-03-02-dataflow-conf-module.md b/go/ql/lib/change-notes/2023-03-02-dataflow-conf-module.md new file mode 100644 index 00000000000..89190af399f --- /dev/null +++ b/go/ql/lib/change-notes/2023-03-02-dataflow-conf-module.md @@ -0,0 +1,9 @@ +--- +category: majorAnalysis +--- +* The main data flow and taint tracking APIs have been changed. The old APIs + remain in place for now and translate to the new through a + backwards-compatible wrapper. If multiple configurations are in scope + simultaneously, then this may affect results slightly. The new API is quite + similar to the old, but makes use of a configuration module instead of a + configuration class. diff --git a/go/ql/lib/semmle/go/dataflow/DataFlow.qll b/go/ql/lib/semmle/go/dataflow/DataFlow.qll index d99ce3bb554..fde408a4450 100644 --- a/go/ql/lib/semmle/go/dataflow/DataFlow.qll +++ b/go/ql/lib/semmle/go/dataflow/DataFlow.qll @@ -22,7 +22,8 @@ import go * data flow analysis. */ module DataFlow { - import semmle.go.dataflow.internal.DataFlowImpl + import semmle.go.dataflow.internal.DataFlow + import semmle.go.dataflow.internal.DataFlowImpl1 import Properties } diff --git a/go/ql/lib/semmle/go/dataflow/TaintTracking.qll b/go/ql/lib/semmle/go/dataflow/TaintTracking.qll index 096116d4bee..2f0bb5ea116 100644 --- a/go/ql/lib/semmle/go/dataflow/TaintTracking.qll +++ b/go/ql/lib/semmle/go/dataflow/TaintTracking.qll @@ -10,5 +10,6 @@ import semmle.go.dataflow.DataFlow * global (inter-procedural) taint-tracking analyses. */ module TaintTracking { + import semmle.go.dataflow.internal.tainttracking1.TaintTracking import semmle.go.dataflow.internal.tainttracking1.TaintTrackingImpl } diff --git a/go/ql/lib/semmle/go/dataflow/internal/DataFlow.qll b/go/ql/lib/semmle/go/dataflow/internal/DataFlow.qll new file mode 100644 index 00000000000..fe9b9b18941 --- /dev/null +++ b/go/ql/lib/semmle/go/dataflow/internal/DataFlow.qll @@ -0,0 +1,245 @@ +/** + * Provides an implementation of global (interprocedural) data flow. This file + * re-exports the local (intraprocedural) data flow analysis from + * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed + * through the `Make` and `MakeWithState` modules. + */ + +private import DataFlowImplCommon +private import DataFlowImplSpecific::Private +import DataFlowImplSpecific::Public +import DataFlowImplCommonPublic +private import DataFlowImpl + +/** An input configuration for data flow. */ +signature module ConfigSig { + /** + * Holds if `source` is a relevant data flow source. + */ + predicate isSource(Node source); + + /** + * Holds if `sink` is a relevant data flow sink. + */ + predicate isSink(Node sink); + + /** + * Holds if data flow through `node` is prohibited. This completely removes + * `node` from the data flow graph. + */ + default predicate isBarrier(Node node) { none() } + + /** Holds if data flow into `node` is prohibited. */ + default predicate isBarrierIn(Node node) { none() } + + /** Holds if data flow out of `node` is prohibited. */ + default predicate isBarrierOut(Node node) { none() } + + /** + * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. + */ + default predicate isAdditionalFlowStep(Node node1, Node node2) { none() } + + /** + * Holds if an arbitrary number of implicit read steps of content `c` may be + * taken at `node`. + */ + default predicate allowImplicitRead(Node node, ContentSet c) { none() } + + /** + * Gets the virtual dispatch branching limit when calculating field flow. + * This can be overridden to a smaller value to improve performance (a + * value of 0 disables field flow), or a larger value to get more results. + */ + default int fieldFlowBranchLimit() { result = 2 } + + /** + * Gets a data flow configuration feature to add restrictions to the set of + * valid flow paths. + * + * - `FeatureHasSourceCallContext`: + * Assume that sources have some existing call context to disallow + * conflicting return-flow directly following the source. + * - `FeatureHasSinkCallContext`: + * Assume that sinks have some existing call context to disallow + * conflicting argument-to-parameter flow directly preceding the sink. + * - `FeatureEqualSourceSinkCallContext`: + * Implies both of the above and additionally ensures that the entire flow + * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. + */ + default FlowFeature getAFeature() { none() } + + /** Holds if sources should be grouped in the result of `hasFlowPath`. */ + default predicate sourceGrouping(Node source, string sourceGroup) { none() } + + /** Holds if sinks should be grouped in the result of `hasFlowPath`. */ + default predicate sinkGrouping(Node sink, string sinkGroup) { none() } + + /** + * Holds if hidden nodes should be included in the data flow graph. + * + * This feature should only be used for debugging or when the data flow graph + * is not visualized (as it is in a `path-problem` query). + */ + default predicate includeHiddenNodes() { none() } +} + +/** An input configuration for data flow using flow state. */ +signature module StateConfigSig { + bindingset[this] + class FlowState; + + /** + * Holds if `source` is a relevant data flow source with the given initial + * `state`. + */ + predicate isSource(Node source, FlowState state); + + /** + * Holds if `sink` is a relevant data flow sink accepting `state`. + */ + predicate isSink(Node sink, FlowState state); + + /** + * Holds if data flow through `node` is prohibited. This completely removes + * `node` from the data flow graph. + */ + default predicate isBarrier(Node node) { none() } + + /** + * Holds if data flow through `node` is prohibited when the flow state is + * `state`. + */ + predicate isBarrier(Node node, FlowState state); + + /** Holds if data flow into `node` is prohibited. */ + default predicate isBarrierIn(Node node) { none() } + + /** Holds if data flow out of `node` is prohibited. */ + default predicate isBarrierOut(Node node) { none() } + + /** + * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. + */ + default predicate isAdditionalFlowStep(Node node1, Node node2) { none() } + + /** + * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. + * This step is only applicable in `state1` and updates the flow state to `state2`. + */ + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2); + + /** + * Holds if an arbitrary number of implicit read steps of content `c` may be + * taken at `node`. + */ + default predicate allowImplicitRead(Node node, ContentSet c) { none() } + + /** + * Gets the virtual dispatch branching limit when calculating field flow. + * This can be overridden to a smaller value to improve performance (a + * value of 0 disables field flow), or a larger value to get more results. + */ + default int fieldFlowBranchLimit() { result = 2 } + + /** + * Gets a data flow configuration feature to add restrictions to the set of + * valid flow paths. + * + * - `FeatureHasSourceCallContext`: + * Assume that sources have some existing call context to disallow + * conflicting return-flow directly following the source. + * - `FeatureHasSinkCallContext`: + * Assume that sinks have some existing call context to disallow + * conflicting argument-to-parameter flow directly preceding the sink. + * - `FeatureEqualSourceSinkCallContext`: + * Implies both of the above and additionally ensures that the entire flow + * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. + */ + default FlowFeature getAFeature() { none() } + + /** Holds if sources should be grouped in the result of `hasFlowPath`. */ + default predicate sourceGrouping(Node source, string sourceGroup) { none() } + + /** Holds if sinks should be grouped in the result of `hasFlowPath`. */ + default predicate sinkGrouping(Node sink, string sinkGroup) { none() } + + /** + * Holds if hidden nodes should be included in the data flow graph. + * + * This feature should only be used for debugging or when the data flow graph + * is not visualized (as it is in a `path-problem` query). + */ + default predicate includeHiddenNodes() { none() } +} + +/** + * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` + * measured in approximate number of interprocedural steps. + */ +signature int explorationLimitSig(); + +/** + * The output of a data flow computation. + */ +signature module DataFlowSig { + /** + * A `Node` augmented with a call context (except for sinks) and an access path. + * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. + */ + class PathNode; + + /** + * Holds if data can flow from `source` to `sink`. + * + * The corresponding paths are generated from the end-points and the graph + * included in the module `PathGraph`. + */ + predicate hasFlowPath(PathNode source, PathNode sink); + + /** + * Holds if data can flow from `source` to `sink`. + */ + predicate hasFlow(Node source, Node sink); + + /** + * Holds if data can flow from some source to `sink`. + */ + predicate hasFlowTo(Node sink); + + /** + * Holds if data can flow from some source to `sink`. + */ + predicate hasFlowToExpr(DataFlowExpr sink); +} + +/** + * Constructs a standard data flow computation. + */ +module Make implements DataFlowSig { + private module C implements FullStateConfigSig { + import DefaultState + import Config + } + + import Impl +} + +/** + * Constructs a data flow computation using flow state. + */ +module MakeWithState implements DataFlowSig { + private module C implements FullStateConfigSig { + import Config + } + + import Impl +} diff --git a/go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl.qll b/go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl.qll index 1b969756b09..0afcba55582 100644 --- a/go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl.qll +++ b/go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl.qll @@ -1,135 +1,75 @@ /** - * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from - * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed - * through the `Configuration` class. This file exists in several identical - * copies, allowing queries to use multiple `Configuration` classes that depend - * on each other without introducing mutual recursion among those configurations. + * INTERNAL: Do not use. + * + * Provides an implementation of global (interprocedural) data flow. */ private import DataFlowImplCommon private import DataFlowImplSpecific::Private -import DataFlowImplSpecific::Public -import DataFlowImplCommonPublic +private import DataFlowImplSpecific::Public +private import DataFlowImplCommonPublic +import DataFlow /** - * A configuration of interprocedural data flow analysis. This defines - * sources, sinks, and any other configurable aspect of the analysis. Each - * use of the global data flow library must define its own unique extension - * of this abstract class. To create a configuration, extend this class with - * a subclass whose characteristic predicate is a unique singleton string. - * For example, write - * - * ```ql - * class MyAnalysisConfiguration extends DataFlow::Configuration { - * MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" } - * // Override `isSource` and `isSink`. - * // Optionally override `isBarrier`. - * // Optionally override `isAdditionalFlowStep`. - * } - * ``` - * Conceptually, this defines a graph where the nodes are `DataFlow::Node`s and - * the edges are those data-flow steps that preserve the value of the node - * along with any additional edges defined by `isAdditionalFlowStep`. - * Specifying nodes in `isBarrier` will remove those nodes from the graph, and - * specifying nodes in `isBarrierIn` and/or `isBarrierOut` will remove in-going - * and/or out-going edges from those nodes, respectively. - * - * Then, to query whether there is flow between some `source` and `sink`, - * write - * - * ```ql - * exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink)) - * ``` - * - * Multiple configurations can coexist, but two classes extending - * `DataFlow::Configuration` should never depend on each other. One of them - * should instead depend on a `DataFlow2::Configuration`, a - * `DataFlow3::Configuration`, or a `DataFlow4::Configuration`. + * An input configuration for data flow using flow state. This signature equals + * `StateConfigSig`, but requires explicit implementation of all predicates. */ -abstract class Configuration extends string { +signature module FullStateConfigSig { bindingset[this] - Configuration() { any() } - - /** - * Holds if `source` is a relevant data flow source. - */ - predicate isSource(Node source) { none() } + class FlowState; /** * Holds if `source` is a relevant data flow source with the given initial * `state`. */ - predicate isSource(Node source, FlowState state) { none() } - - /** - * Holds if `sink` is a relevant data flow sink. - */ - predicate isSink(Node sink) { none() } + predicate isSource(Node source, FlowState state); /** * Holds if `sink` is a relevant data flow sink accepting `state`. */ - predicate isSink(Node sink, FlowState state) { none() } + predicate isSink(Node sink, FlowState state); /** * Holds if data flow through `node` is prohibited. This completely removes * `node` from the data flow graph. */ - predicate isBarrier(Node node) { none() } + predicate isBarrier(Node node); /** * Holds if data flow through `node` is prohibited when the flow state is * `state`. */ - predicate isBarrier(Node node, FlowState state) { none() } + predicate isBarrier(Node node, FlowState state); /** Holds if data flow into `node` is prohibited. */ - predicate isBarrierIn(Node node) { none() } + predicate isBarrierIn(Node node); /** Holds if data flow out of `node` is prohibited. */ - predicate isBarrierOut(Node node) { none() } - - /** - * DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead. - * - * Holds if data flow through nodes guarded by `guard` is prohibited. - */ - deprecated predicate isBarrierGuard(BarrierGuard guard) { none() } - - /** - * DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead. - * - * Holds if data flow through nodes guarded by `guard` is prohibited when - * the flow state is `state` - */ - deprecated predicate isBarrierGuard(BarrierGuard guard, FlowState state) { none() } + predicate isBarrierOut(Node node); /** * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. */ - predicate isAdditionalFlowStep(Node node1, Node node2) { none() } + predicate isAdditionalFlowStep(Node node1, Node node2); /** * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. * This step is only applicable in `state1` and updates the flow state to `state2`. */ - predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { - none() - } + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2); /** * Holds if an arbitrary number of implicit read steps of content `c` may be * taken at `node`. */ - predicate allowImplicitRead(Node node, ContentSet c) { none() } + predicate allowImplicitRead(Node node, ContentSet c); /** * Gets the virtual dispatch branching limit when calculating field flow. * This can be overridden to a smaller value to improve performance (a * value of 0 disables field flow), or a larger value to get more results. */ - int fieldFlowBranchLimit() { result = 2 } + int fieldFlowBranchLimit(); /** * Gets a data flow configuration feature to add restrictions to the set of @@ -144,1720 +84,680 @@ abstract class Configuration extends string { * - `FeatureEqualSourceSinkCallContext`: * Implies both of the above and additionally ensures that the entire flow * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. */ - FlowFeature getAFeature() { none() } + FlowFeature getAFeature(); /** Holds if sources should be grouped in the result of `hasFlowPath`. */ - predicate sourceGrouping(Node source, string sourceGroup) { none() } + predicate sourceGrouping(Node source, string sourceGroup); /** Holds if sinks should be grouped in the result of `hasFlowPath`. */ - predicate sinkGrouping(Node sink, string sinkGroup) { none() } - - /** - * Holds if data may flow from `source` to `sink` for this configuration. - */ - predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } - - /** - * Holds if data may flow from `source` to `sink` for this configuration. - * - * The corresponding paths are generated from the end-points and the graph - * included in the module `PathGraph`. - */ - predicate hasFlowPath(PathNode source, PathNode sink) { hasFlowPath(source, sink, this) } - - /** - * Holds if data may flow from some source to `sink` for this configuration. - */ - predicate hasFlowTo(Node sink) { - sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode() - } - - /** - * Holds if data may flow from some source to `sink` for this configuration. - */ - predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } - - /** - * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` - * measured in approximate number of interprocedural steps. - */ - int explorationLimit() { none() } + predicate sinkGrouping(Node sink, string sinkGroup); /** * Holds if hidden nodes should be included in the data flow graph. * * This feature should only be used for debugging or when the data flow graph - * is not visualized (for example in a `path-problem` query). + * is not visualized (as it is in a `path-problem` query). */ - predicate includeHiddenNodes() { none() } + predicate includeHiddenNodes(); +} - /** - * Holds if there is a partial data flow path from `source` to `node`. The - * approximate distance between `node` and the closest source is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards sink definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sources is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - */ - final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { - partialFlow(source, node, this) and - dist = node.getSourceDistance() - } +/** + * Provides default `FlowState` implementations given a `StateConfigSig`. + */ +module DefaultState { + class FlowState = Unit; - /** - * Holds if there is a partial data flow path from `node` to `sink`. The - * approximate distance between `node` and the closest sink is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards source definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sinks is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - * - * Note that reverse flow has slightly lower precision than the corresponding - * forward flow, as reverse flow disregards type pruning among other features. - */ - final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { - revPartialFlow(node, sink, this) and - dist = node.getSinkDistance() + predicate isSource(Node source, FlowState state) { Config::isSource(source) and exists(state) } + + predicate isSink(Node sink, FlowState state) { Config::isSink(sink) and exists(state) } + + predicate isBarrier(Node node, FlowState state) { none() } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + none() } } /** - * This class exists to prevent mutual recursion between the user-overridden - * member predicates of `Configuration` and the rest of the data-flow library. - * Good performance cannot be guaranteed in the presence of such recursion, so - * it should be replaced by using more than one copy of the data flow library. + * Constructs a data flow computation given a full input configuration. */ -abstract private class ConfigurationRecursionPrevention extends Configuration { - bindingset[this] - ConfigurationRecursionPrevention() { any() } +module Impl { + private class FlowState = Config::FlowState; - override predicate hasFlow(Node source, Node sink) { - strictcount(Node n | this.isSource(n)) < 0 - or - strictcount(Node n | this.isSource(n, _)) < 0 - or - strictcount(Node n | this.isSink(n)) < 0 - or - strictcount(Node n | this.isSink(n, _)) < 0 - or - strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0 - or - strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, _, n2, _)) < 0 - or - super.hasFlow(source, sink) - } -} + private newtype TNodeEx = + TNodeNormal(Node n) or + TNodeImplicitRead(Node n, boolean hasRead) { + Config::allowImplicitRead(n, _) and hasRead = [false, true] + } -private newtype TNodeEx = - TNodeNormal(Node n) or - TNodeImplicitRead(Node n, boolean hasRead) { - any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true] - } - -private class NodeEx extends TNodeEx { - string toString() { - result = this.asNode().toString() - or - exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") - } - - Node asNode() { this = TNodeNormal(result) } - - predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } - - Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } - - pragma[nomagic] - private DataFlowCallable getEnclosingCallable0() { - nodeEnclosingCallable(this.projectToNode(), result) - } - - pragma[inline] - DataFlowCallable getEnclosingCallable() { - pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) - } - - pragma[nomagic] - private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } - - pragma[inline] - DataFlowType getDataFlowType() { - pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) - } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -private class ArgNodeEx extends NodeEx { - ArgNodeEx() { this.asNode() instanceof ArgNode } -} - -private class ParamNodeEx extends NodeEx { - ParamNodeEx() { this.asNode() instanceof ParamNode } - - predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { - this.asNode().(ParamNode).isParameterOf(c, pos) - } - - ParameterPosition getPosition() { this.isParameterOf(_, result) } -} - -private class RetNodeEx extends NodeEx { - RetNodeEx() { this.asNode() instanceof ReturnNodeExt } - - ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } - - ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } -} - -private predicate inBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierIn(n) - | - config.isSource(n) or config.isSource(n, _) - ) -} - -private predicate outBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierOut(n) - | - config.isSink(n) or config.isSink(n, _) - ) -} - -/** A bridge class to access the deprecated `isBarrierGuard`. */ -private class BarrierGuardGuardedNodeBridge extends Unit { - abstract predicate guardedNode(Node n, Configuration config); - - abstract predicate guardedNode(Node n, FlowState state, Configuration config); -} - -private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { - deprecated override predicate guardedNode(Node n, Configuration config) { - exists(BarrierGuard g | - config.isBarrierGuard(g) and - n = g.getAGuardedNode() - ) - } - - deprecated override predicate guardedNode(Node n, FlowState state, Configuration config) { - exists(BarrierGuard g | - config.isBarrierGuard(g, state) and - n = g.getAGuardedNode() - ) - } -} - -pragma[nomagic] -private predicate fullBarrier(NodeEx node, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n) - or - config.isBarrierIn(n) and - not config.isSource(n) and - not config.isSource(n, _) - or - config.isBarrierOut(n) and - not config.isSink(n) and - not config.isSink(n, _) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config) - ) -} - -pragma[nomagic] -private predicate stateBarrier(NodeEx node, FlowState state, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n, state) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config) - ) -} - -pragma[nomagic] -private predicate sourceNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSource(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSource(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -pragma[nomagic] -private predicate sinkNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSink(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSink(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -/** Provides the relevant barriers for a step from `node1` to `node2`. */ -pragma[inline] -private predicate stepFilter(NodeEx node1, NodeEx node2, Configuration config) { - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) -} - -/** - * Holds if data can flow in one local step from `node1` to `node2`. - */ -private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.asNode() = n and - node2.isImplicitReadNode(n, false) and - not fullBarrier(node1, config) - ) -} - -/** - * Holds if the additional step from `node1` to `node2` does not jump between callables. - */ -private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.isImplicitReadNode(n, true) and - node2.asNode() = n and - not fullBarrier(node2, config) - ) -} - -private predicate additionalLocalStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) - ) -} - -/** - * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. - */ -private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if the additional step from `node1` to `node2` jumps between callables. - */ -private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate additionalJumpStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -pragma[nomagic] -private predicate readSet(NodeEx node1, ContentSet c, NodeEx node2, Configuration config) { - readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and - stepFilter(node1, node2, config) - or - exists(Node n | - node2.isImplicitReadNode(n, true) and - node1.isImplicitReadNode(n, _) and - config.allowImplicitRead(n, c) - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(ContentSet cs | - readSet(node1, cs, node2, config) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate clearsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - clearsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate expectsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - expectsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -pragma[nomagic] -private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } - -pragma[nomagic] -private predicate hasReadStep(Content c, Configuration config) { read(_, c, _, config) } - -pragma[nomagic] -private predicate store( - NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config -) { - store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), - contentType) and - hasReadStep(tc.getContent(), config) and - stepFilter(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { - viableReturnPosOut(call, pos, out.asNode()) -} - -pragma[nomagic] -private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { - viableParamArg(call, p.asNode(), arg.asNode()) -} - -/** - * Holds if field flow should be used for the given configuration. - */ -private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } - -private predicate hasSourceCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSourceCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate hasSinkCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSinkCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if flow from `p` to a return node of kind `kind` is allowed. - * - * We don't expect a parameter to return stored in itself, unless - * explicitly allowed - */ -bindingset[p, kind] -private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { - exists(ParameterPosition pos | p.isParameterOf(_, pos) | - not kind.(ParamUpdateReturnKind).getPosition() = pos - or - allowParameterReturnInSelfCached(p.asNode()) - ) -} - -private module Stage1 implements StageSig { - class Ap extends int { - // workaround for bad functionality-induced joins (happens when using `Unit`) - pragma[nomagic] - Ap() { this in [0 .. 1] and this < 1 } - } - - private class Cc = boolean; - - /* Begin: Stage 1 logic. */ - /** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `cc` records whether the node is reached through an - * argument in a call. - */ - private predicate fwdFlow(NodeEx node, Cc cc, Configuration config) { - sourceNode(node, _, config) and - if hasSourceCallCtx(config) then cc = true else cc = false - or - exists(NodeEx mid | fwdFlow(mid, cc, config) | - localFlowStep(mid, node, config) or - additionalLocalFlowStep(mid, node, config) or - additionalLocalStateStep(mid, _, node, _, config) - ) - or - exists(NodeEx mid | fwdFlow(mid, _, config) and cc = false | - jumpStep(mid, node, config) or - additionalJumpStep(mid, node, config) or - additionalJumpStateStep(mid, _, node, _, config) - ) - or - // store - exists(NodeEx mid | - useFieldFlow(config) and - fwdFlow(mid, cc, config) and - store(mid, _, node, _, config) - ) - or - // read - exists(ContentSet c | - fwdFlowReadSet(c, node, cc, config) and - fwdFlowConsCandSet(c, _, config) - ) - or - // flow into a callable - fwdFlowIn(_, _, _, node, config) and - cc = true - or - // flow out of a callable - fwdFlowOut(_, node, false, config) and - cc = false - or - // flow through a callable - exists(DataFlowCall call | - fwdFlowOutFromArg(call, node, config) and - fwdFlowIsEntered(call, cc, config) - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowIn( - DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p, Configuration config - ) { - // call context cannot help reduce virtual dispatch - fwdFlow(arg, cc, config) and - viableParamArgEx(call, p, arg) and - not fullBarrier(p, config) and - ( - cc = false + private class NodeEx extends TNodeEx { + string toString() { + result = this.asNode().toString() or - cc = true and - not reducedViableImplInCallContext(call, _, _) - ) - or - // call context may help reduce virtual dispatch - exists(DataFlowCallable target | - fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target, config) and - target = viableImplInSomeFwdFlowCallContextExt(call, config) and - cc = true - ) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - fwdFlowIn(call, _, cc, _, config) - } - - pragma[nomagic] - private predicate fwdFlowInReducedViableImplInSomeCallContext( - DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target, Configuration config - ) { - fwdFlow(arg, true, config) and - viableParamArgEx(call, p, arg) and - reducedViableImplInCallContext(call, _, _) and - target = p.getEnclosingCallable() and - not fullBarrier(p, config) - } - - /** - * Gets a viable dispatch target of `call` in the context `ctx`. This is - * restricted to those `call`s for which a context might make a difference, - * and to `ctx`s that are reachable in `fwdFlow`. - */ - pragma[nomagic] - private DataFlowCallable viableImplInSomeFwdFlowCallContextExt( - DataFlowCall call, Configuration config - ) { - exists(DataFlowCall ctx | - fwdFlowIsEntered(ctx, _, config) and - result = viableImplInCallContextExt(call, ctx) - ) - } - - private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) } - - pragma[nomagic] - private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc, Configuration config) { - exists(NodeEx mid | - fwdFlow(mid, cc, config) and - readSet(mid, c, node, config) - ) - } - - /** - * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - fwdFlow(mid, _, config) and - store(mid, tc, node, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `cs` may be interpreted in a read as the target of some store - * into `c`, in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCandSet(ContentSet cs, Content c, Configuration config) { - fwdFlowConsCand(c, config) and - c = cs.getAReadContent() - } - - pragma[nomagic] - private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { - exists(RetNodeEx ret | - fwdFlow(ret, cc, config) and - ret.getReturnPosition() = pos - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) { - exists(ReturnPosition pos | - fwdFlowReturnPosition(pos, cc, config) and - viableReturnPosOutEx(call, pos, out) and - not fullBarrier(out, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) { - fwdFlowOut(call, out, true, config) - } - - private predicate stateStepFwd(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1 | - additionalLocalStateStep(node1, state1, _, state2, config) or - additionalJumpStateStep(node1, state1, _, state2, config) - | - fwdFlow(node1, config) - ) - } - - private predicate fwdFlowState(FlowState state, Configuration config) { - sourceNode(_, state, config) - or - exists(FlowState state0 | - fwdFlowState(state0, config) and - stateStepFwd(state0, state, config) - ) - } - - /** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink. - */ - pragma[nomagic] - private predicate revFlow(NodeEx node, boolean toReturn, Configuration config) { - revFlow0(node, toReturn, config) and - fwdFlow(node, config) - } - - pragma[nomagic] - private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) { - exists(FlowState state | - fwdFlow(node, pragma[only_bind_into](config)) and - sinkNode(node, state, config) and - fwdFlowState(state, pragma[only_bind_into](config)) and - if hasSinkCallCtx(config) then toReturn = true else toReturn = false - ) - or - exists(NodeEx mid | revFlow(mid, toReturn, config) | - localFlowStep(node, mid, config) or - additionalLocalFlowStep(node, mid, config) or - additionalLocalStateStep(node, _, mid, _, config) - ) - or - exists(NodeEx mid | revFlow(mid, _, config) and toReturn = false | - jumpStep(node, mid, config) or - additionalJumpStep(node, mid, config) or - additionalJumpStateStep(node, _, mid, _, config) - ) - or - // store - exists(Content c | - revFlowStore(c, node, toReturn, config) and - revFlowConsCand(c, config) - ) - or - // read - exists(NodeEx mid, ContentSet c | - readSet(node, c, mid, config) and - fwdFlowConsCandSet(c, _, pragma[only_bind_into](config)) and - revFlow(mid, toReturn, pragma[only_bind_into](config)) - ) - or - // flow into a callable - revFlowIn(_, node, false, config) and - toReturn = false - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(pos, config) and - node.(RetNodeEx).getReturnPosition() = pos and - toReturn = true - ) - or - // flow through a callable - exists(DataFlowCall call | - revFlowInToReturn(call, node, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - /** - * Holds if `c` is the target of a read in the flow covered by `revFlow`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, ContentSet cs | - fwdFlow(node, pragma[only_bind_into](config)) and - readSet(node, cs, mid, config) and - fwdFlowConsCandSet(cs, c, pragma[only_bind_into](config)) and - revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) { - exists(NodeEx mid, TypedContent tc | - revFlow(mid, toReturn, pragma[only_bind_into](config)) and - fwdFlowConsCand(c, pragma[only_bind_into](config)) and - store(node, tc, mid, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `c` is the target of both a read and a store in the flow covered - * by `revFlow`. - */ - pragma[nomagic] - additional predicate revFlowIsReadAndStored(Content c, Configuration conf) { - revFlowConsCand(c, conf) and - revFlowStore(c, _, _, conf) - } - - pragma[nomagic] - additional predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config - ) { - fwdFlowReturnPosition(pos, _, config) and - viableReturnPosOutEx(call, pos, out) - } - - pragma[nomagic] - private predicate revFlowOut(ReturnPosition pos, Configuration config) { - exists(NodeEx out | - revFlow(out, _, config) and - viableReturnPosOutNodeCandFwd1(_, pos, out, config) - ) - } - - pragma[nomagic] - additional predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config - ) { - fwdFlowIn(call, arg, _, p, config) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate revFlowIn( - DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config - ) { - exists(ParamNodeEx p | - revFlow(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) { - revFlowIn(call, arg, true, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(NodeEx out | - revFlow(out, toReturn, config) and - fwdFlowOutFromArg(call, out, config) - ) - } - - private predicate stateStepRev(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - additionalLocalStateStep(node1, state1, node2, state2, config) or - additionalJumpStateStep(node1, state1, node2, state2, config) - | - revFlow(node1, _, pragma[only_bind_into](config)) and - revFlow(node2, _, pragma[only_bind_into](config)) and - fwdFlowState(state1, pragma[only_bind_into](config)) and - fwdFlowState(state2, pragma[only_bind_into](config)) - ) - } - - additional predicate revFlowState(FlowState state, Configuration config) { - exists(NodeEx node | - sinkNode(node, state, config) and - revFlow(node, _, pragma[only_bind_into](config)) and - fwdFlowState(state, pragma[only_bind_into](config)) - ) - or - exists(FlowState state0 | - revFlowState(state0, config) and - stateStepRev(state, state0, config) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Content c | - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - revFlow(node2, pragma[only_bind_into](config)) and - store(node1, tc, node2, contentType, config) and - c = tc.getContent() and - exists(ap1) - ) - } - - pragma[nomagic] - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) { - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - read(n1, c, n2, pragma[only_bind_into](config)) and - revFlow(n2, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, config) and - exists(ap) - } - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, _, pragma[only_bind_into](config)) and - exists(state) and - exists(ap) - } - - private predicate throughFlowNodeCand(NodeEx node, Configuration config) { - revFlow(node, true, config) and - fwdFlow(node, true, config) and - not inBarrier(node, config) and - not outBarrier(node, config) - } - - /** Holds if flow may return from `callable`. */ - pragma[nomagic] - private predicate returnFlowCallableNodeCand( - DataFlowCallable callable, ReturnKindExt kind, Configuration config - ) { - exists(RetNodeEx ret | - throughFlowNodeCand(ret, config) and - callable = ret.getEnclosingCallable() and - kind = ret.getKind() - ) - } - - /** - * Holds if flow may enter through `p` and reach a return node making `p` a - * candidate for the origin of a summary. - */ - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(DataFlowCallable c, ReturnKindExt kind | - throughFlowNodeCand(p, config) and - returnFlowCallableNodeCand(c, kind, config) and - p.getEnclosingCallable() = c and - exists(ap) and - parameterFlowThroughAllowed(p, kind) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - throughFlowNodeCand(ret, config) and - kind = ret.getKind() and - exists(argAp) and - exists(ap) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, boolean toReturn | - revFlow(arg, toReturn, config) and - revFlowInToReturn(call, arg, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, config)) and - fields = count(Content f0 | fwdFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | fwdFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, config)) and - fields = count(Content f0 | revFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | revFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | revFlow(n, b, config)) - } - /* End: Stage 1 logic. */ -} - -pragma[noinline] -private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - localFlowStep(node1, node2, config) -} - -pragma[noinline] -private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - additionalLocalFlowStep(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCand1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config -) { - Stage1::revFlow(out, config) and - Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, Configuration config -) { - exists(ReturnPosition pos | - viableReturnPosOutNodeCand1(call, pos, out, config) and - pos = ret.getReturnPosition() and - kind = pos.getKind() and - Stage1::revFlow(ret, config) and - not outBarrier(ret, config) and - not inBarrier(out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCand1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config -) { - Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and - Stage1::revFlow(arg, config) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config -) { - viableParamArgNodeCand1(call, p, arg, config) and - Stage1::revFlow(p, config) and - not outBarrier(arg, config) and - not inBarrier(p, config) -} - -/** - * Gets the amount of forward branching on the origin of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int branch(NodeEx n1, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n1, _, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) - ) -} - -/** - * Gets the amount of backward branching on the target of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int join(NodeEx n2, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n, _, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) - ) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. The - * `allowsFieldFlow` flag indicates whether the branching is within the limit - * specified by the configuration. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, ret, kind, out, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(ret, pragma[only_bind_into](config)) and - j = join(out, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. The `allowsFieldFlow` flag indicates whether - * the branching is within the limit specified by the configuration. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config -) { - flowIntoCallNodeCand1(call, arg, p, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(arg, pragma[only_bind_into](config)) and - j = join(p, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -private signature module StageSig { - class Ap; - - predicate revFlow(NodeEx node, Configuration config); - - predicate revFlowAp(NodeEx node, Ap ap, Configuration config); - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config); - - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config); - - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config); - - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ); - - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ); - - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config); -} - -private module MkStage { - class ApApprox = PrevStage::Ap; - - signature module StageParam { - class Ap; - - class ApNil extends Ap; - - bindingset[result, ap] - ApApprox getApprox(Ap ap); - - ApNil getApNil(NodeEx node); - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail); - - /** - * An approximation of `Content` that corresponds to the precision level of - * `Ap`, such that the mappings from both `Ap` and `Content` to this type - * are functional. - */ - class ApHeadContent; - - ApHeadContent getHeadContent(Ap ap); - - ApHeadContent projectToHeadContent(Content c); - - class ApOption; - - ApOption apNone(); - - ApOption apSome(Ap ap); - - class Cc; - - class CcCall extends Cc; - - // TODO: member predicate on CcCall - predicate matchesCall(CcCall cc, DataFlowCall call); - - class CcNoCall extends Cc; - - Cc ccNone(); - - CcCall ccSomeCall(); - - class LocalCc; - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc); - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ); - - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config - ); - - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config - ); - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config); - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType); - } - - module Stage implements StageSig { - import Param - - /* Begin: Stage logic. */ - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - private predicate revFlowApAlias(NodeEx node, ApApprox apa, Configuration config) { - PrevStage::revFlowAp(node, apa, config) + exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") } - pragma[nomagic] - private predicate flowIntoCallApa( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa, - Configuration config - ) { - flowIntoCall(call, arg, p, allowsFieldFlow, config) and - PrevStage::revFlowAp(p, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(arg, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } + Node asNode() { this = TNodeNormal(result) } + + predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } + + Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } pragma[nomagic] - private predicate flowOutOfCallApa( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - ApApprox apa, Configuration config - ) { - flowOutOfCall(call, ret, kind, out, allowsFieldFlow, config) and - PrevStage::revFlowAp(out, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(ret, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowThroughOutOfCall( - DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, - ApApprox argApa, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa, pragma[only_bind_into](config)) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) and - matchesCall(ccc, call) - ) - } - - /** - * Holds if `node` is reachable with access path `ap` from a source in the - * configuration `config`. - * - * The call context `cc` records whether the node is reached through an - * argument in a call, and if so, `summaryCtx` and `argAp` record the - * corresponding parameter position and access path of that argument, respectively. - */ - pragma[nomagic] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa, config) and - PrevStage::revFlow(node, state, apa, config) and - filter(node, state, ap, config) + private DataFlowCallable getEnclosingCallable0() { + nodeEnclosingCallable(this.projectToNode(), result) } pragma[inline] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, cc, summaryCtx, argAp, ap, _, config) + DataFlowCallable getEnclosingCallable() { + pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) } pragma[nomagic] - private predicate fwdFlow0( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config + private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } + + pragma[inline] + DataFlowType getDataFlowType() { + pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) + } + + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn ) { - sourceNode(node, state, config) and - (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and - argAp = apNone() and - summaryCtx = TParamNodeNone() and - ap = getApNil(node) and - apa = getApprox(ap) + this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + private class ArgNodeEx extends NodeEx { + ArgNodeEx() { this.asNode() instanceof ArgNode } + } + + private class ParamNodeEx extends NodeEx { + ParamNodeEx() { this.asNode() instanceof ParamNode } + + predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { + this.asNode().(ParamNode).isParameterOf(c, pos) + } + + ParameterPosition getPosition() { this.isParameterOf(_, result) } + } + + private class RetNodeEx extends NodeEx { + RetNodeEx() { this.asNode() instanceof ReturnNodeExt } + + ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } + + ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } + } + + private predicate inBarrier(NodeEx node) { + exists(Node n | + node.asNode() = n and + Config::isBarrierIn(n) and + Config::isSource(n, _) + ) + } + + private predicate outBarrier(NodeEx node) { + exists(Node n | + node.asNode() = n and + Config::isBarrierOut(n) and + Config::isSink(n, _) + ) + } + + pragma[nomagic] + private predicate fullBarrier(NodeEx node) { + exists(Node n | node.asNode() = n | + Config::isBarrier(n) or - exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | - fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0, config) and - localCc = getLocalCc(mid, cc) - | - localStep(mid, state0, node, state, true, _, config, localCc) and - ap = ap0 and - apa = apa0 - or - localStep(mid, state0, node, state, false, ap, config, localCc) and - ap0 instanceof ApNil and - apa = getApprox(ap) + Config::isBarrierIn(n) and + not Config::isSource(n, _) + or + Config::isBarrierOut(n) and + not Config::isSink(n, _) + ) + } + + pragma[nomagic] + private predicate stateBarrier(NodeEx node, FlowState state) { + exists(Node n | node.asNode() = n | Config::isBarrier(n, state)) + } + + pragma[nomagic] + private predicate sourceNode(NodeEx node, FlowState state) { + Config::isSource(node.asNode(), state) and + not fullBarrier(node) and + not stateBarrier(node, state) + } + + pragma[nomagic] + private predicate sinkNode(NodeEx node, FlowState state) { + Config::isSink(node.asNode(), state) and + not fullBarrier(node) and + not stateBarrier(node, state) + } + + /** Provides the relevant barriers for a step from `node1` to `node2`. */ + pragma[inline] + private predicate stepFilter(NodeEx node1, NodeEx node2) { + not outBarrier(node1) and + not inBarrier(node2) and + not fullBarrier(node1) and + not fullBarrier(node2) + } + + /** + * Holds if data can flow in one local step from `node1` to `node2`. + */ + private predicate localFlowStepEx(NodeEx node1, NodeEx node2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and + stepFilter(node1, node2) + ) + or + exists(Node n | + Config::allowImplicitRead(n, _) and + node1.asNode() = n and + node2.isImplicitReadNode(n, false) and + not fullBarrier(node1) + ) + } + + /** + * Holds if the additional step from `node1` to `node2` does not jump between callables. + */ + private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + Config::isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and + getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and + stepFilter(node1, node2) + ) + or + exists(Node n | + Config::allowImplicitRead(n, _) and + node1.isImplicitReadNode(n, true) and + node2.asNode() = n and + not fullBarrier(node2) + ) + } + + private predicate additionalLocalStateStep(NodeEx node1, FlowState s1, NodeEx node2, FlowState s2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + Config::isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and + getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and + stepFilter(node1, node2) and + not stateBarrier(node1, s1) and + not stateBarrier(node2, s2) + ) + } + + /** + * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. + */ + private predicate jumpStepEx(NodeEx node1, NodeEx node2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and + stepFilter(node1, node2) and + not Config::getAFeature() instanceof FeatureEqualSourceSinkCallContext + ) + } + + /** + * Holds if the additional step from `node1` to `node2` jumps between callables. + */ + private predicate additionalJumpStep(NodeEx node1, NodeEx node2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + Config::isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and + getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and + stepFilter(node1, node2) and + not Config::getAFeature() instanceof FeatureEqualSourceSinkCallContext + ) + } + + private predicate additionalJumpStateStep(NodeEx node1, FlowState s1, NodeEx node2, FlowState s2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + Config::isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and + getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and + stepFilter(node1, node2) and + not stateBarrier(node1, s1) and + not stateBarrier(node2, s2) and + not Config::getAFeature() instanceof FeatureEqualSourceSinkCallContext + ) + } + + pragma[nomagic] + private predicate readSetEx(NodeEx node1, ContentSet c, NodeEx node2) { + readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and + stepFilter(node1, node2) + or + exists(Node n | + node2.isImplicitReadNode(n, true) and + node1.isImplicitReadNode(n, _) and + Config::allowImplicitRead(n, c) + ) + } + + // inline to reduce fan-out via `getAReadContent` + bindingset[c] + private predicate read(NodeEx node1, Content c, NodeEx node2) { + exists(ContentSet cs | + readSetEx(node1, cs, node2) and + pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() + ) + } + + // inline to reduce fan-out via `getAReadContent` + bindingset[c] + private predicate clearsContentEx(NodeEx n, Content c) { + exists(ContentSet cs | + clearsContentCached(n.asNode(), cs) and + pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() + ) + } + + // inline to reduce fan-out via `getAReadContent` + bindingset[c] + private predicate expectsContentEx(NodeEx n, Content c) { + exists(ContentSet cs | + expectsContentCached(n.asNode(), cs) and + pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() + ) + } + + pragma[nomagic] + private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } + + pragma[nomagic] + private predicate hasReadStep(Content c) { read(_, c, _) } + + pragma[nomagic] + private predicate storeEx(NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType) { + store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), + contentType) and + hasReadStep(tc.getContent()) and + stepFilter(node1, node2) + } + + pragma[nomagic] + private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { + viableReturnPosOut(call, pos, out.asNode()) + } + + pragma[nomagic] + private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { + viableParamArg(call, p.asNode(), arg.asNode()) + } + + /** + * Holds if field flow should be used for the given configuration. + */ + private predicate useFieldFlow() { Config::fieldFlowBranchLimit() >= 1 } + + private predicate hasSourceCallCtx() { + exists(FlowFeature feature | feature = Config::getAFeature() | + feature instanceof FeatureHasSourceCallContext or + feature instanceof FeatureEqualSourceSinkCallContext + ) + } + + private predicate hasSinkCallCtx() { + exists(FlowFeature feature | feature = Config::getAFeature() | + feature instanceof FeatureHasSinkCallContext or + feature instanceof FeatureEqualSourceSinkCallContext + ) + } + + /** + * Holds if flow from `p` to a return node of kind `kind` is allowed. + * + * We don't expect a parameter to return stored in itself, unless + * explicitly allowed + */ + bindingset[p, kind] + private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { + exists(ParameterPosition pos | p.isParameterOf(_, pos) | + not kind.(ParamUpdateReturnKind).getPosition() = pos + or + allowParameterReturnInSelfCached(p.asNode()) + ) + } + + private module Stage1 implements StageSig { + class Ap extends int { + // workaround for bad functionality-induced joins (happens when using `Unit`) + pragma[nomagic] + Ap() { this in [0 .. 1] and this < 1 } + } + + private class Cc = boolean; + + /* Begin: Stage 1 logic. */ + /** + * Holds if `node` is reachable from a source. + * + * The Boolean `cc` records whether the node is reached through an + * argument in a call. + */ + private predicate fwdFlow(NodeEx node, Cc cc) { + sourceNode(node, _) and + if hasSourceCallCtx() then cc = true else cc = false + or + exists(NodeEx mid | fwdFlow(mid, cc) | + localFlowStepEx(mid, node) or + additionalLocalFlowStep(mid, node) or + additionalLocalStateStep(mid, _, node, _) ) or - exists(NodeEx mid | - fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa, pragma[only_bind_into](config)) and - jumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(mid, state, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(mid, state0, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStateStep(mid, state0, node, state, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) + exists(NodeEx mid | fwdFlow(mid, _) and cc = false | + jumpStepEx(mid, node) or + additionalJumpStep(mid, node) or + additionalJumpStateStep(mid, _, node, _) ) or // store - exists(TypedContent tc, Ap ap0 | - fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp, config) and - ap = apCons(tc, ap0) and - apa = getApprox(ap) + exists(NodeEx mid | + useFieldFlow() and + fwdFlow(mid, cc) and + storeEx(mid, _, node, _) ) or // read - exists(Ap ap0, Content c | - fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp, config) and - fwdFlowConsCand(ap0, c, ap, config) and - apa = getApprox(ap) + exists(ContentSet c | + fwdFlowReadSet(c, node, cc) and + fwdFlowConsCandSet(c, _) ) or // flow into a callable - fwdFlowIn(_, node, state, _, cc, _, _, ap, apa, config) and - if PrevStage::parameterMayFlowThrough(node, apa, config) - then ( - summaryCtx = TParamNodeSome(node.asNode()) and - argAp = apSome(ap) - ) else ( - summaryCtx = TParamNodeNone() and argAp = apNone() - ) + fwdFlowIn(_, _, _, node) and + cc = true or // flow out of a callable - exists( - DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, - DataFlowCallable inner - | - fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa, config) and - flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa, config) and - inner = ret.getEnclosingCallable() and - cc = getCallContextReturn(inner, call, innercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) + fwdFlowOut(_, node, false) and + cc = false or // flow through a callable - exists( - DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, ApApprox innerArgApa - | - fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowStore( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(DataFlowType contentType, ApApprox apa1 | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1, config) and - PrevStage::storeStepCand(node1, apa1, tc, node2, contentType, config) and - typecheckStore(ap1, contentType) - ) - } - - /** - * Holds if forward flow with access path `tail` reaches a store of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(TypedContent tc | - fwdFlowStore(_, tail, tc, _, _, _, _, _, config) and - tc.getContent() = c and - cons = apCons(tc, tail) - ) - } - - pragma[nomagic] - private predicate readStepCand( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - PrevStage::readStepCand(node1, c, node2, config) and - apc = projectToHeadContent(c) - } - - bindingset[node1, apc] - pragma[inline_late] - private predicate readStepCand0( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - readStepCand(node1, apc, c, node2, config) - } - - pragma[nomagic] - private predicate fwdFlowRead( - Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(ApHeadContent apc | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap, config) and - apc = getHeadContent(ap) and - readStepCand0(node1, apc, c, node2, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowIn( - DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, - ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa, Configuration config - ) { - exists(ArgNodeEx arg, boolean allowsFieldFlow | - fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa, config) and - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowRetFromArg( - RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, ApApprox argApa, - Ap ap, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - fwdFlow(pragma[only_bind_into](ret), state, ccc, - TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), - pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa), - pragma[only_bind_into](config)) and - kind = ret.getKind() and - parameterFlowThroughAllowed(summaryCtx, kind) and - argApa = getApprox(argAp) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) + exists(DataFlowCall call | + fwdFlowOutFromArg(call, node) and + fwdFlowIsEntered(call, cc) ) } + // inline to reduce the number of iterations pragma[inline] - private predicate fwdFlowThrough0( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, - Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa, config) and - fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp, config) - } - - pragma[nomagic] - private predicate fwdFlowThrough( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa, - config) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` - * and data might flow through the target callable and back out at `call`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered( - DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, - ParamNodeEx p, Ap ap, Configuration config - ) { - exists(ApApprox apa | - fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(p, apa, config) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate storeStepFwd( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config - ) { - fwdFlowStore(node1, ap1, tc, node2, _, _, _, _, config) and - ap2 = apCons(tc, ap1) and - fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _, config) - } - - private predicate readStepFwd( - NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config - ) { - fwdFlowRead(ap1, c, n1, n2, _, _, _, _, config) and - fwdFlowConsCand(ap1, c, ap2, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough0( - DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, - ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, - innerArgApa, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough( - RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, - Ap ap, Configuration config - ) { - exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | - returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa, config) and - pos = ret.getReturnPosition() and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowThroughIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap, - Configuration config - ) { - exists(ApApprox argApa | - flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), - allowsFieldFlow, argApa, pragma[only_bind_into](config)) and - fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa, - pragma[only_bind_into](config)) and - returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap, - pragma[only_bind_into](config)) and - if allowsFieldFlow = false then argAp instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowIntoCallAp( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap, - Configuration config - ) { - exists(ApApprox apa | - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - fwdFlow(arg, _, _, _, _, ap, apa, config) - ) - } - - pragma[nomagic] - private predicate flowOutOfCallAp( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, - Ap ap, Configuration config - ) { - exists(ApApprox apa | - flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa, config) and - fwdFlow(ret, _, _, _, _, ap, apa, config) and - pos = ret.getReturnPosition() - ) - } - - /** - * Holds if `node` with access path `ap` is part of a path from a source to a - * sink in the configuration `config`. - * - * The parameter `returnCtx` records whether (and how) the node must be returned - * from the enclosing callable in order to reach a sink, and if so, `returnAp` - * records the access path of the returned value. - */ - pragma[nomagic] - additional predicate revFlow( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - revFlow0(node, state, returnCtx, returnAp, ap, config) and - fwdFlow(node, state, _, _, _, ap, config) - } - - pragma[nomagic] - private predicate revFlow0( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, _, _, _, ap, config) and - sinkNode(node, state, config) and + private predicate fwdFlowIn(DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p) { + // call context cannot help reduce virtual dispatch + fwdFlow(arg, cc) and + viableParamArgEx(call, p, arg) and + not fullBarrier(p) and ( - if hasSinkCallCtx(config) - then returnCtx = TReturnCtxNoFlowThrough() - else returnCtx = TReturnCtxNone() - ) and - returnAp = apNone() and - ap instanceof ApNil - or - exists(NodeEx mid, FlowState state0 | - localStep(node, state, mid, state0, true, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, ap, config) + cc = false + or + cc = true and + not reducedViableImplInCallContext(call, _, _) ) or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap, pragma[only_bind_into](config)) and - localStep(node, pragma[only_bind_into](state), mid, state0, false, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, nil, pragma[only_bind_into](config)) and - ap instanceof ApNil + // call context may help reduce virtual dispatch + exists(DataFlowCallable target | + fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target) and + target = viableImplInSomeFwdFlowCallContextExt(call) and + cc = true ) - or + } + + /** + * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. + */ + pragma[nomagic] + private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc) { fwdFlowIn(call, _, cc, _) } + + pragma[nomagic] + private predicate fwdFlowInReducedViableImplInSomeCallContext( + DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target + ) { + fwdFlow(arg, true) and + viableParamArgEx(call, p, arg) and + reducedViableImplInCallContext(call, _, _) and + target = p.getEnclosingCallable() and + not fullBarrier(p) + } + + /** + * Gets a viable dispatch target of `call` in the context `ctx`. This is + * restricted to those `call`s for which a context might make a difference, + * and to `ctx`s that are reachable in `fwdFlow`. + */ + pragma[nomagic] + private DataFlowCallable viableImplInSomeFwdFlowCallContextExt(DataFlowCall call) { + exists(DataFlowCall ctx | + fwdFlowIsEntered(ctx, _) and + result = viableImplInCallContextExt(call, ctx) + ) + } + + private predicate fwdFlow(NodeEx node) { fwdFlow(node, _) } + + pragma[nomagic] + private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc) { exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, state, _, _, ap, config) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() + fwdFlow(mid, cc) and + readSetEx(mid, c, node) + ) + } + + /** + * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. + */ + pragma[assume_small_delta] + pragma[nomagic] + private predicate fwdFlowConsCand(Content c) { + exists(NodeEx mid, NodeEx node, TypedContent tc | + not fullBarrier(node) and + useFieldFlow() and + fwdFlow(mid, _) and + storeEx(mid, tc, node, _) and + c = tc.getContent() + ) + } + + /** + * Holds if `cs` may be interpreted in a read as the target of some store + * into `c`, in the flow covered by `fwdFlow`. + */ + pragma[nomagic] + private predicate fwdFlowConsCandSet(ContentSet cs, Content c) { + fwdFlowConsCand(c) and + c = cs.getAReadContent() + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc) { + exists(RetNodeEx ret | + fwdFlow(ret, cc) and + ret.getReturnPosition() = pos + ) + } + + // inline to reduce the number of iterations + pragma[inline] + private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc) and + viableReturnPosOutEx(call, pos, out) and + not fullBarrier(out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out) { + fwdFlowOut(call, out, true) + } + + private predicate stateStepFwd(FlowState state1, FlowState state2) { + exists(NodeEx node1 | + additionalLocalStateStep(node1, state1, _, state2) or + additionalJumpStateStep(node1, state1, _, state2) + | + fwdFlow(node1) + ) + } + + private predicate fwdFlowState(FlowState state) { + sourceNode(_, state) + or + exists(FlowState state0 | + fwdFlowState(state0) and + stateStepFwd(state0, state) + ) + } + + /** + * Holds if `node` is part of a path from a source to a sink. + * + * The Boolean `toReturn` records whether the node must be returned from + * the enclosing callable in order to reach a sink. + */ + pragma[nomagic] + private predicate revFlow(NodeEx node, boolean toReturn) { + revFlow0(node, toReturn) and + fwdFlow(node) + } + + pragma[nomagic] + private predicate revFlow0(NodeEx node, boolean toReturn) { + exists(FlowState state | + fwdFlow(node) and + sinkNode(node, state) and + fwdFlowState(state) and + if hasSinkCallCtx() then toReturn = true else toReturn = false ) or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStep(node, mid, config) and - revFlow(pragma[only_bind_into](mid), state, _, _, nil, pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil + exists(NodeEx mid | revFlow(mid, toReturn) | + localFlowStepEx(node, mid) or + additionalLocalFlowStep(node, mid) or + additionalLocalStateStep(node, _, mid, _) ) or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStateStep(node, state, mid, state0, config) and - revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil, - pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil + exists(NodeEx mid | revFlow(mid, _) and toReturn = false | + jumpStepEx(node, mid) or + additionalJumpStep(node, mid) or + additionalJumpStateStep(node, _, mid, _) ) or // store - exists(Ap ap0, Content c | - revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp, config) and - revFlowConsCand(ap0, c, ap, config) + exists(Content c | + revFlowStore(c, node, toReturn) and + revFlowConsCand(c) ) or // read - exists(NodeEx mid, Ap ap0 | - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - readStepFwd(node, ap, _, mid, ap0, config) + exists(NodeEx mid, ContentSet c | + readSetEx(node, c, mid) and + fwdFlowConsCandSet(c, _) and + revFlow(mid, toReturn) ) or // flow into a callable - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, state, TReturnCtxNone(), returnAp, ap, config) and - flowIntoCallAp(_, node, p, allowsFieldFlow, ap, config) and - (if allowsFieldFlow = false then ap instanceof ApNil else any()) and - returnCtx = TReturnCtxNone() - ) - or - // flow through a callable - exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, node, p, _, ap, innerReturnAp, config) - ) + revFlowIn(_, node, false) and + toReturn = false or // flow out of a callable exists(ReturnPosition pos | - revFlowOut(_, node, pos, state, _, _, ap, config) and - if returnFlowsThrough(node, pos, state, _, _, _, ap, config) - then ( - returnCtx = TReturnCtxMaybeFlowThrough(pos) and - returnAp = apSome(ap) - ) else ( - returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() - ) + revFlowOut(pos) and + node.(RetNodeEx).getReturnPosition() = pos and + toReturn = true + ) + or + // flow through a callable + exists(DataFlowCall call | + revFlowInToReturn(call, node) and + revFlowIsReturned(call, toReturn) ) - } - - pragma[nomagic] - private predicate revFlowStore( - Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, - ReturnCtx returnCtx, ApOption returnAp, Configuration config - ) { - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - storeStepFwd(node, ap, tc, mid, ap0, config) and - tc.getContent() = c } /** - * Holds if reverse flow with access path `tail` reaches a read of `c` - * resulting in access path `cons`. + * Holds if `c` is the target of a read in the flow covered by `revFlow`. */ pragma[nomagic] - private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(NodeEx mid, Ap tail0 | - revFlow(mid, _, _, _, tail, config) and - tail = pragma[only_bind_into](tail0) and - readStepFwd(_, cons, c, mid, tail0, config) + private predicate revFlowConsCand(Content c) { + exists(NodeEx mid, NodeEx node, ContentSet cs | + fwdFlow(node) and + readSetEx(node, cs, mid) and + fwdFlowConsCandSet(cs, c) and + revFlow(pragma[only_bind_into](mid), _) ) } pragma[nomagic] - private predicate revFlowOut( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, - ApOption returnAp, Ap ap, Configuration config + private predicate revFlowStore(Content c, NodeEx node, boolean toReturn) { + exists(NodeEx mid, TypedContent tc | + revFlow(mid, toReturn) and + fwdFlowConsCand(c) and + storeEx(node, tc, mid, _) and + c = tc.getContent() + ) + } + + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + pragma[nomagic] + additional predicate revFlowIsReadAndStored(Content c) { + revFlowConsCand(c) and + revFlowStore(c, _, _) + } + + pragma[nomagic] + additional predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, NodeEx out ) { - exists(NodeEx out, boolean allowsFieldFlow | - revFlow(out, state, returnCtx, returnAp, ap, config) and - flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() + fwdFlowReturnPosition(pos, _) and + viableReturnPosOutEx(call, pos, out) + } + + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos) { + exists(NodeEx out | + revFlow(out, _) and + viableReturnPosOutNodeCandFwd1(_, pos, out) ) } pragma[nomagic] - private predicate revFlowParamToReturn( - ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap, Configuration config - ) { - revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), - pragma[only_bind_into](ap), pragma[only_bind_into](config)) and - parameterFlowThroughAllowed(p, pos.getKind()) and - PrevStage::parameterMayFlowThrough(p, getApprox(ap), config) + additional predicate viableParamArgNodeCandFwd1(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { + fwdFlowIn(call, arg, _, p) + } + + // inline to reduce the number of iterations + pragma[inline] + private predicate revFlowIn(DataFlowCall call, ArgNodeEx arg, boolean toReturn) { + exists(ParamNodeEx p | + revFlow(p, toReturn) and + viableParamArgNodeCandFwd1(call, p, arg) + ) } pragma[nomagic] - private predicate revFlowThrough( - DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, - ApOption returnAp, Ap ap, Ap innerReturnAp, Configuration config - ) { - revFlowParamToReturn(p, state, pos, innerReturnAp, ap, config) and - revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp, config) + private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg) { + revFlowIn(call, arg, true) } /** @@ -1866,896 +766,1704 @@ private module MkStage { * reaching an argument of `call`. */ pragma[nomagic] - private predicate revFlowIsReturned( - DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap, - Configuration config - ) { - exists(RetNodeEx ret, FlowState state, CcCall ccc | - revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap, config) and - returnFlowsThrough(ret, pos, state, ccc, _, _, ap, config) and - matchesCall(ccc, call) + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn) { + exists(NodeEx out | + revFlow(out, toReturn) and + fwdFlowOutFromArg(call, out) + ) + } + + private predicate stateStepRev(FlowState state1, FlowState state2) { + exists(NodeEx node1, NodeEx node2 | + additionalLocalStateStep(node1, state1, node2, state2) or + additionalJumpStateStep(node1, state1, node2, state2) + | + revFlow(node1, _) and + revFlow(node2, _) and + fwdFlowState(state1) and + fwdFlowState(state2) + ) + } + + pragma[nomagic] + additional predicate revFlowState(FlowState state) { + exists(NodeEx node | + sinkNode(node, state) and + revFlow(node, _) and + fwdFlowState(state) + ) + or + exists(FlowState state0 | + revFlowState(state0) and + stateStepRev(state, state0) ) } pragma[nomagic] predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType ) { - exists(Ap ap2, Content c | - PrevStage::storeStepCand(node1, _, tc, node2, contentType, config) and - revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _, config) and - revFlowConsCand(ap2, c, ap1, config) - ) - } - - predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(Ap ap1, Ap ap2 | - revFlow(node2, _, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and - readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _, - pragma[only_bind_into](config)) - ) - } - - additional predicate revFlow(NodeEx node, FlowState state, Configuration config) { - revFlow(node, state, _, _, _, config) - } - - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, _, _, ap, config) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, _, _, _, ap, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, Configuration config) { - revFlow(node, _, _, _, _, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, ap, config) - } - - private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepFwd(_, ap, tc, _, _, config) - } - - private predicate revConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepCand(_, ap, tc, _, _, config) - } - - private predicate validAp(Ap ap, Configuration config) { - revFlow(_, _, _, _, ap, config) and ap instanceof ApNil - or - exists(TypedContent head, Ap tail | - consCand(head, tail, config) and - ap = apCons(head, tail) - ) - } - - additional predicate consCand(TypedContent tc, Ap ap, Configuration config) { - revConsCand(tc, ap, config) and - validAp(ap, config) - } - - pragma[nomagic] - private predicate parameterFlowsThroughRev( - ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp, Configuration config - ) { - revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap, config) and - parameterFlowThroughAllowed(p, pos.getKind()) - } - - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(ReturnPosition pos | - returnFlowsThrough(_, pos, _, _, p, ap, _, config) and - parameterFlowsThroughRev(p, ap, pos, _, config) + exists(Content c | + revFlowIsReadAndStored(c) and + revFlow(node2) and + storeEx(node1, tc, node2, contentType) and + c = tc.getContent() and + exists(ap1) ) } pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - exists(ParamNodeEx p, ReturnPosition pos | - returnFlowsThrough(ret, pos, _, _, p, argAp, ap, config) and - parameterFlowsThroughRev(p, argAp, pos, ap, config) and - kind = pos.getKind() + predicate readStepCand(NodeEx n1, Content c, NodeEx n2) { + revFlowIsReadAndStored(c) and + read(n1, c, n2) and + revFlow(n2) + } + + pragma[nomagic] + predicate revFlow(NodeEx node) { revFlow(node, _) } + + pragma[nomagic] + predicate revFlowAp(NodeEx node, Ap ap) { + revFlow(node) and + exists(ap) + } + + bindingset[node, state] + predicate revFlow(NodeEx node, FlowState state, Ap ap) { + revFlow(node, _) and + exists(state) and + exists(ap) + } + + private predicate throughFlowNodeCand(NodeEx node) { + revFlow(node, true) and + fwdFlow(node, true) and + not inBarrier(node) and + not outBarrier(node) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand(DataFlowCallable callable, ReturnKindExt kind) { + exists(RetNodeEx ret | + throughFlowNodeCand(ret) and + callable = ret.getEnclosingCallable() and + kind = ret.getKind() + ) + } + + /** + * Holds if flow may enter through `p` and reach a return node making `p` a + * candidate for the origin of a summary. + */ + pragma[nomagic] + predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap) { + exists(DataFlowCallable c, ReturnKindExt kind | + throughFlowNodeCand(p) and + returnFlowCallableNodeCand(c, kind) and + p.getEnclosingCallable() = c and + exists(ap) and + parameterFlowThroughAllowed(p, kind) ) } pragma[nomagic] - private predicate revFlowThroughArg( - DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, - Ap ap, Configuration config - ) { - exists(ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp, config) - ) + predicate returnMayFlowThrough(RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind) { + throughFlowNodeCand(ret) and + kind = ret.getKind() and + exists(argAp) and + exists(ap) } pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | - revFlow(arg, state, returnCtx, returnAp, ap, config) and - revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap, config) + predicate callMayFlowThroughRev(DataFlowCall call) { + exists(ArgNodeEx arg, boolean toReturn | + revFlow(arg, toReturn) and + revFlowInToReturn(call, arg) and + revFlowIsReturned(call, toReturn) ) } additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config + boolean fwd, int nodes, int fields, int conscand, int states, int tuples ) { fwd = true and - nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, config)) and - fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and - states = count(FlowState state | fwdFlow(_, state, _, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | - fwdFlow(n, state, cc, summaryCtx, argAp, ap, config) - ) + nodes = count(NodeEx node | fwdFlow(node)) and + fields = count(Content f0 | fwdFlowConsCand(f0)) and + conscand = -1 and + states = count(FlowState state | fwdFlowState(state)) and + tuples = count(NodeEx n, boolean b | fwdFlow(n, b)) or fwd = false and - nodes = count(NodeEx node | revFlow(node, _, _, _, _, config)) and - fields = count(TypedContent f0 | consCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and - states = count(FlowState state | revFlow(_, state, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | - revFlow(n, state, returnCtx, retAp, ap, config) - ) + nodes = count(NodeEx node | revFlow(node, _)) and + fields = count(Content f0 | revFlowConsCand(f0)) and + conscand = -1 and + states = count(FlowState state | revFlowState(state)) and + tuples = count(NodeEx n, boolean b | revFlow(n, b)) } - /* End: Stage logic. */ - } -} - -private module BooleanCallContext { - class Cc extends boolean { - Cc() { this in [true, false] } + /* End: Stage 1 logic. */ } - class CcCall extends Cc { - CcCall() { this = true } + pragma[noinline] + private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2) { + Stage1::revFlow(node2) and + localFlowStepEx(node1, node2) } - /** Holds if the call context may be `call`. */ - predicate matchesCall(CcCall cc, DataFlowCall call) { any() } - - class CcNoCall extends Cc { - CcNoCall() { this = false } + pragma[noinline] + private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2) { + Stage1::revFlow(node2) and + additionalLocalFlowStep(node1, node2) } - Cc ccNone() { result = false } + pragma[nomagic] + private predicate viableReturnPosOutNodeCand1(DataFlowCall call, ReturnPosition pos, NodeEx out) { + Stage1::revFlow(out) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out) + } - CcCall ccSomeCall() { result = true } + /** + * Holds if data can flow out of `call` from `ret` to `out`, either + * through a `ReturnNode` or through an argument that has been mutated, and + * that this step is part of a path from a source to a sink. + */ + pragma[nomagic] + private predicate flowOutOfCallNodeCand1( + DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out + ) { + exists(ReturnPosition pos | + viableReturnPosOutNodeCand1(call, pos, out) and + pos = ret.getReturnPosition() and + kind = pos.getKind() and + Stage1::revFlow(ret) and + not outBarrier(ret) and + not inBarrier(out) + ) + } - class LocalCc = Unit; + pragma[nomagic] + private predicate viableParamArgNodeCand1(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { + Stage1::viableParamArgNodeCandFwd1(call, p, arg) and + Stage1::revFlow(arg) + } - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } + /** + * Holds if data can flow into `call` and that this step is part of a + * path from a source to a sink. + */ + pragma[nomagic] + private predicate flowIntoCallNodeCand1(DataFlowCall call, ArgNodeEx arg, ParamNodeEx p) { + viableParamArgNodeCand1(call, p, arg) and + Stage1::revFlow(p) and + not outBarrier(arg) and + not inBarrier(p) + } - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + /** + * Gets the amount of forward branching on the origin of a cross-call path + * edge in the graph of paths between sources and sinks that ignores call + * contexts. + */ + pragma[nomagic] + private int branch(NodeEx n1) { + result = + strictcount(NodeEx n | flowOutOfCallNodeCand1(_, n1, _, n) or flowIntoCallNodeCand1(_, n1, n)) + } - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } -} + /** + * Gets the amount of backward branching on the target of a cross-call path + * edge in the graph of paths between sources and sinks that ignores call + * contexts. + */ + pragma[nomagic] + private int join(NodeEx n2) { + result = + strictcount(NodeEx n | flowOutOfCallNodeCand1(_, n, _, n2) or flowIntoCallNodeCand1(_, n, n2)) + } -private module Level1CallContext { - class Cc = CallContext; + /** + * Holds if data can flow out of `call` from `ret` to `out`, either + * through a `ReturnNode` or through an argument that has been mutated, and + * that this step is part of a path from a source to a sink. The + * `allowsFieldFlow` flag indicates whether the branching is within the limit + * specified by the configuration. + */ + pragma[nomagic] + private predicate flowOutOfCallNodeCand1( + DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow + ) { + flowOutOfCallNodeCand1(call, ret, kind, out) and + exists(int b, int j | + b = branch(ret) and + j = join(out) and + if b.minimum(j) <= Config::fieldFlowBranchLimit() + then allowsFieldFlow = true + else allowsFieldFlow = false + ) + } - class CcCall = CallContextCall; + /** + * Holds if data can flow into `call` and that this step is part of a + * path from a source to a sink. The `allowsFieldFlow` flag indicates whether + * the branching is within the limit specified by the configuration. + */ + pragma[nomagic] + private predicate flowIntoCallNodeCand1( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow + ) { + flowIntoCallNodeCand1(call, arg, p) and + exists(int b, int j | + b = branch(arg) and + j = join(p) and + if b.minimum(j) <= Config::fieldFlowBranchLimit() + then allowsFieldFlow = true + else allowsFieldFlow = false + ) + } - pragma[inline] - predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } + private signature module StageSig { + class Ap; - class CcNoCall = CallContextNoCall; + predicate revFlow(NodeEx node); - Cc ccNone() { result instanceof CallContextAny } + predicate revFlowAp(NodeEx node, Ap ap); - CcCall ccSomeCall() { result instanceof CallContextSomeCall } + bindingset[node, state] + predicate revFlow(NodeEx node, FlowState state, Ap ap); + + predicate callMayFlowThroughRev(DataFlowCall call); + + predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap); + + predicate returnMayFlowThrough(RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind); + + predicate storeStepCand( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType + ); + + predicate readStepCand(NodeEx n1, Content c, NodeEx n2); + } + + private module MkStage { + class ApApprox = PrevStage::Ap; + + signature module StageParam { + class Ap; + + class ApNil extends Ap; + + bindingset[result, ap] + ApApprox getApprox(Ap ap); + + ApNil getApNil(NodeEx node); + + bindingset[tc, tail] + Ap apCons(TypedContent tc, Ap tail); + + /** + * An approximation of `Content` that corresponds to the precision level of + * `Ap`, such that the mappings from both `Ap` and `Content` to this type + * are functional. + */ + class ApHeadContent; + + ApHeadContent getHeadContent(Ap ap); + + ApHeadContent projectToHeadContent(Content c); + + class ApOption; + + ApOption apNone(); + + ApOption apSome(Ap ap); + + class Cc; + + class CcCall extends Cc; + + // TODO: member predicate on CcCall + predicate matchesCall(CcCall cc, DataFlowCall call); + + class CcNoCall extends Cc; + + Cc ccNone(); + + CcCall ccSomeCall(); + + class LocalCc; + + bindingset[call, c, outercc] + CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); + + bindingset[call, c, innercc] + CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); + + bindingset[node, cc] + LocalCc getLocalCc(NodeEx node, Cc cc); + + bindingset[node1, state1] + bindingset[node2, state2] + predicate localStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + ApNil ap, LocalCc lcc + ); + + predicate flowOutOfCall( + DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow + ); + + predicate flowIntoCall( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow + ); + + bindingset[node, state, ap] + predicate filter(NodeEx node, FlowState state, Ap ap); + + bindingset[ap, contentType] + predicate typecheckStore(Ap ap, DataFlowType contentType); + } + + module Stage implements StageSig { + import Param + + /* Begin: Stage logic. */ + // use an alias as a workaround for bad functionality-induced joins + pragma[nomagic] + private predicate revFlowApAlias(NodeEx node, ApApprox apa) { + PrevStage::revFlowAp(node, apa) + } + + pragma[nomagic] + private predicate flowIntoCallApa( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa + ) { + flowIntoCall(call, arg, p, allowsFieldFlow) and + PrevStage::revFlowAp(p, pragma[only_bind_into](apa)) and + revFlowApAlias(arg, pragma[only_bind_into](apa)) + } + + pragma[nomagic] + private predicate flowOutOfCallApa( + DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, + ApApprox apa + ) { + flowOutOfCall(call, ret, kind, out, allowsFieldFlow) and + PrevStage::revFlowAp(out, pragma[only_bind_into](apa)) and + revFlowApAlias(ret, pragma[only_bind_into](apa)) + } + + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, + ApApprox argApa, ApApprox apa + ) { + exists(ReturnKindExt kind | + flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa) and + PrevStage::callMayFlowThroughRev(call) and + PrevStage::returnMayFlowThrough(ret, argApa, apa, kind) and + matchesCall(ccc, call) + ) + } + + /** + * Holds if `node` is reachable with access path `ap` from a source. + * + * The call context `cc` records whether the node is reached through an + * argument in a call, and if so, `summaryCtx` and `argAp` record the + * corresponding parameter position and access path of that argument, respectively. + */ + pragma[nomagic] + additional predicate fwdFlow( + NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, + ApApprox apa + ) { + fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa) and + PrevStage::revFlow(node, state, apa) and + filter(node, state, ap) + } + + pragma[inline] + additional predicate fwdFlow( + NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap + ) { + fwdFlow(node, state, cc, summaryCtx, argAp, ap, _) + } + + pragma[assume_small_delta] + pragma[nomagic] + private predicate fwdFlow0( + NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, + ApApprox apa + ) { + sourceNode(node, state) and + (if hasSourceCallCtx() then cc = ccSomeCall() else cc = ccNone()) and + argAp = apNone() and + summaryCtx = TParamNodeNone() and + ap = getApNil(node) and + apa = getApprox(ap) + or + exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | + fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0) and + localCc = getLocalCc(mid, cc) + | + localStep(mid, state0, node, state, true, _, localCc) and + ap = ap0 and + apa = apa0 + or + localStep(mid, state0, node, state, false, ap, localCc) and + ap0 instanceof ApNil and + apa = getApprox(ap) + ) + or + exists(NodeEx mid | + fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa) and + jumpStepEx(mid, node) and + cc = ccNone() and + summaryCtx = TParamNodeNone() and + argAp = apNone() + ) + or + exists(NodeEx mid, ApNil nil | + fwdFlow(mid, state, _, _, _, nil) and + additionalJumpStep(mid, node) and + cc = ccNone() and + summaryCtx = TParamNodeNone() and + argAp = apNone() and + ap = getApNil(node) and + apa = getApprox(ap) + ) + or + exists(NodeEx mid, FlowState state0, ApNil nil | + fwdFlow(mid, state0, _, _, _, nil) and + additionalJumpStateStep(mid, state0, node, state) and + cc = ccNone() and + summaryCtx = TParamNodeNone() and + argAp = apNone() and + ap = getApNil(node) and + apa = getApprox(ap) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp) and + ap = apCons(tc, ap0) and + apa = getApprox(ap) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp) and + fwdFlowConsCand(ap0, c, ap) and + apa = getApprox(ap) + ) + or + // flow into a callable + fwdFlowIn(_, node, state, _, cc, _, _, ap, apa) and + if PrevStage::parameterMayFlowThrough(node, apa) + then ( + summaryCtx = TParamNodeSome(node.asNode()) and + argAp = apSome(ap) + ) else ( + summaryCtx = TParamNodeNone() and argAp = apNone() + ) + or + // flow out of a callable + exists( + DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | + fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa) and + flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa) and + inner = ret.getEnclosingCallable() and + cc = getCallContextReturn(inner, call, innercc) and + if allowsFieldFlow = false then ap instanceof ApNil else any() + ) + or + // flow through a callable + exists( + DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, + ApApprox innerArgApa + | + fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa) and + flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa) and + if allowsFieldFlow = false then ap instanceof ApNil else any() + ) + } + + pragma[nomagic] + private predicate fwdFlowStore( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, + ParamNodeOption summaryCtx, ApOption argAp + ) { + exists(DataFlowType contentType, ApApprox apa1 | + fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1) and + PrevStage::storeStepCand(node1, apa1, tc, node2, contentType) and + typecheckStore(ap1, contentType) + ) + } + + /** + * Holds if forward flow with access path `tail` reaches a store of `c` + * resulting in access path `cons`. + */ + pragma[nomagic] + private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail) { + exists(TypedContent tc | + fwdFlowStore(_, tail, tc, _, _, _, _, _) and + tc.getContent() = c and + cons = apCons(tc, tail) + ) + } + + pragma[nomagic] + private predicate readStepCand(NodeEx node1, ApHeadContent apc, Content c, NodeEx node2) { + PrevStage::readStepCand(node1, c, node2) and + apc = projectToHeadContent(c) + } + + bindingset[node1, apc] + pragma[inline_late] + private predicate readStepCand0(NodeEx node1, ApHeadContent apc, Content c, NodeEx node2) { + readStepCand(node1, apc, c, node2) + } + + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, + ParamNodeOption summaryCtx, ApOption argAp + ) { + exists(ApHeadContent apc | + fwdFlow(node1, state, cc, summaryCtx, argAp, ap) and + apc = getHeadContent(ap) and + readStepCand0(node1, apc, c, node2) + ) + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, + ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa + ) { + exists(ArgNodeEx arg, boolean allowsFieldFlow | + fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa) and + flowIntoCallApa(call, arg, p, allowsFieldFlow, apa) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and + if allowsFieldFlow = false then ap instanceof ApNil else any() + ) + } + + pragma[nomagic] + private predicate fwdFlowRetFromArg( + RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, + ApApprox argApa, Ap ap, ApApprox apa + ) { + exists(ReturnKindExt kind | + fwdFlow(pragma[only_bind_into](ret), state, ccc, + TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), + pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa)) and + kind = ret.getKind() and + parameterFlowThroughAllowed(summaryCtx, kind) and + argApa = getApprox(argAp) and + PrevStage::returnMayFlowThrough(ret, argApa, apa, kind) + ) + } + + pragma[inline] + private predicate fwdFlowThrough0( + DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, + ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, + Ap innerArgAp, ApApprox innerArgApa + ) { + fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa) and + fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp) + } + + pragma[nomagic] + private predicate fwdFlowThrough( + DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, + ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa + ) { + fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa) + } + + /** + * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` + * and data might flow through the target callable and back out at `call`. + */ + pragma[nomagic] + private predicate fwdFlowIsEntered( + DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, + ParamNodeEx p, Ap ap + ) { + exists(ApApprox apa | + fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, + pragma[only_bind_into](apa)) and + PrevStage::parameterMayFlowThrough(p, apa) and + PrevStage::callMayFlowThroughRev(call) + ) + } + + pragma[nomagic] + private predicate storeStepFwd(NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2) { + fwdFlowStore(node1, ap1, tc, node2, _, _, _, _) and + ap2 = apCons(tc, ap1) and + fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _) + } + + private predicate readStepFwd(NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2) { + fwdFlowRead(ap1, c, n1, n2, _, _, _, _) and + fwdFlowConsCand(ap1, c, ap2) + } + + pragma[nomagic] + private predicate returnFlowsThrough0( + DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, + ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa + ) { + fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, + innerArgApa) + } + + pragma[nomagic] + private predicate returnFlowsThrough( + RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, + Ap ap + ) { + exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | + returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa) and + flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa) and + pos = ret.getReturnPosition() and + if allowsFieldFlow = false then ap instanceof ApNil else any() + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap + ) { + exists(ApApprox argApa | + flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), + allowsFieldFlow, argApa) and + fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa) and + returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap) and + if allowsFieldFlow = false then argAp instanceof ApNil else any() + ) + } + + pragma[nomagic] + private predicate flowIntoCallAp( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap + ) { + exists(ApApprox apa | + flowIntoCallApa(call, arg, p, allowsFieldFlow, apa) and + fwdFlow(arg, _, _, _, _, ap, apa) + ) + } + + pragma[nomagic] + private predicate flowOutOfCallAp( + DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, + Ap ap + ) { + exists(ApApprox apa | + flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa) and + fwdFlow(ret, _, _, _, _, ap, apa) and + pos = ret.getReturnPosition() + ) + } + + /** + * Holds if `node` with access path `ap` is part of a path from a source to a + * sink. + * + * The parameter `returnCtx` records whether (and how) the node must be returned + * from the enclosing callable in order to reach a sink, and if so, `returnAp` + * records the access path of the returned value. + */ + pragma[nomagic] + additional predicate revFlow( + NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap + ) { + revFlow0(node, state, returnCtx, returnAp, ap) and + fwdFlow(node, state, _, _, _, ap) + } + + pragma[nomagic] + private predicate revFlow0( + NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap + ) { + fwdFlow(node, state, _, _, _, ap) and + sinkNode(node, state) and + ( + if hasSinkCallCtx() + then returnCtx = TReturnCtxNoFlowThrough() + else returnCtx = TReturnCtxNone() + ) and + returnAp = apNone() and + ap instanceof ApNil + or + exists(NodeEx mid, FlowState state0 | + localStep(node, state, mid, state0, true, _, _) and + revFlow(mid, state0, returnCtx, returnAp, ap) + ) + or + exists(NodeEx mid, FlowState state0, ApNil nil | + fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap) and + localStep(node, pragma[only_bind_into](state), mid, state0, false, _, _) and + revFlow(mid, state0, returnCtx, returnAp, nil) and + ap instanceof ApNil + ) + or + exists(NodeEx mid | + jumpStepEx(node, mid) and + revFlow(mid, state, _, _, ap) and + returnCtx = TReturnCtxNone() and + returnAp = apNone() + ) + or + exists(NodeEx mid, ApNil nil | + fwdFlow(node, _, _, _, _, ap) and + additionalJumpStep(node, mid) and + revFlow(pragma[only_bind_into](mid), state, _, _, nil) and + returnCtx = TReturnCtxNone() and + returnAp = apNone() and + ap instanceof ApNil + ) + or + exists(NodeEx mid, FlowState state0, ApNil nil | + fwdFlow(node, _, _, _, _, ap) and + additionalJumpStateStep(node, state, mid, state0) and + revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil) and + returnCtx = TReturnCtxNone() and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp) and + revFlowConsCand(ap0, c, ap) + ) + or + // read + exists(NodeEx mid, Ap ap0 | + revFlow(mid, state, returnCtx, returnAp, ap0) and + readStepFwd(node, ap, _, mid, ap0) + ) + or + // flow into a callable + exists(ParamNodeEx p, boolean allowsFieldFlow | + revFlow(p, state, TReturnCtxNone(), returnAp, ap) and + flowIntoCallAp(_, node, p, allowsFieldFlow, ap) and + (if allowsFieldFlow = false then ap instanceof ApNil else any()) and + returnCtx = TReturnCtxNone() + ) + or + // flow through a callable + exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | + revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp) and + flowThroughIntoCall(call, node, p, _, ap, innerReturnAp) + ) + or + // flow out of a callable + exists(ReturnPosition pos | + revFlowOut(_, node, pos, state, _, _, ap) and + if returnFlowsThrough(node, pos, state, _, _, _, ap) + then ( + returnCtx = TReturnCtxMaybeFlowThrough(pos) and + returnAp = apSome(ap) + ) else ( + returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() + ) + ) + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, + ReturnCtx returnCtx, ApOption returnAp + ) { + revFlow(mid, state, returnCtx, returnAp, ap0) and + storeStepFwd(node, ap, tc, mid, ap0) and + tc.getContent() = c + } + + /** + * Holds if reverse flow with access path `tail` reaches a read of `c` + * resulting in access path `cons`. + */ + pragma[nomagic] + private predicate revFlowConsCand(Ap cons, Content c, Ap tail) { + exists(NodeEx mid, Ap tail0 | + revFlow(mid, _, _, _, tail) and + tail = pragma[only_bind_into](tail0) and + readStepFwd(_, cons, c, mid, tail0) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, + ApOption returnAp, Ap ap + ) { + exists(NodeEx out, boolean allowsFieldFlow | + revFlow(out, state, returnCtx, returnAp, ap) and + flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap) and + if allowsFieldFlow = false then ap instanceof ApNil else any() + ) + } + + pragma[nomagic] + private predicate revFlowParamToReturn( + ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap + ) { + revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), + pragma[only_bind_into](ap)) and + parameterFlowThroughAllowed(p, pos.getKind()) and + PrevStage::parameterMayFlowThrough(p, getApprox(ap)) + } + + pragma[nomagic] + private predicate revFlowThrough( + DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, + ApOption returnAp, Ap ap, Ap innerReturnAp + ) { + revFlowParamToReturn(p, state, pos, innerReturnAp, ap) and + revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. + */ + pragma[nomagic] + private predicate revFlowIsReturned( + DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap + ) { + exists(RetNodeEx ret, FlowState state, CcCall ccc | + revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap) and + returnFlowsThrough(ret, pos, state, ccc, _, _, ap) and + matchesCall(ccc, call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType + ) { + exists(Ap ap2, Content c | + PrevStage::storeStepCand(node1, _, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _) and + revFlowConsCand(ap2, c, ap1) + ) + } + + predicate readStepCand(NodeEx node1, Content c, NodeEx node2) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, _, pragma[only_bind_into](ap2)) and + readStepFwd(node1, ap1, c, node2, ap2) and + revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _) + ) + } + + additional predicate revFlow(NodeEx node, FlowState state) { revFlow(node, state, _, _, _) } + + predicate revFlow(NodeEx node, FlowState state, Ap ap) { revFlow(node, state, _, _, ap) } + + pragma[nomagic] + predicate revFlow(NodeEx node) { revFlow(node, _, _, _, _) } + + pragma[nomagic] + predicate revFlowAp(NodeEx node, Ap ap) { revFlow(node, _, _, _, ap) } + + // use an alias as a workaround for bad functionality-induced joins + pragma[nomagic] + additional predicate revFlowAlias(NodeEx node) { revFlow(node, _, _, _, _) } + + // use an alias as a workaround for bad functionality-induced joins + pragma[nomagic] + additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap) { + revFlow(node, state, ap) + } + + private predicate fwdConsCand(TypedContent tc, Ap ap) { storeStepFwd(_, ap, tc, _, _) } + + private predicate revConsCand(TypedContent tc, Ap ap) { storeStepCand(_, ap, tc, _, _) } + + private predicate validAp(Ap ap) { + revFlow(_, _, _, _, ap) and ap instanceof ApNil + or + exists(TypedContent head, Ap tail | + consCand(head, tail) and + ap = apCons(head, tail) + ) + } + + additional predicate consCand(TypedContent tc, Ap ap) { + revConsCand(tc, ap) and + validAp(ap) + } + + pragma[nomagic] + private predicate parameterFlowsThroughRev( + ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp + ) { + revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap) and + parameterFlowThroughAllowed(p, pos.getKind()) + } + + pragma[nomagic] + predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap) { + exists(ReturnPosition pos | + returnFlowsThrough(_, pos, _, _, p, ap, _) and + parameterFlowsThroughRev(p, ap, pos, _) + ) + } + + pragma[nomagic] + predicate returnMayFlowThrough(RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind) { + exists(ParamNodeEx p, ReturnPosition pos | + returnFlowsThrough(ret, pos, _, _, p, argAp, ap) and + parameterFlowsThroughRev(p, argAp, pos, ap) and + kind = pos.getKind() + ) + } + + pragma[nomagic] + private predicate revFlowThroughArg( + DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, + Ap ap + ) { + exists(ParamNodeEx p, Ap innerReturnAp | + revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp) and + flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp) + ) + } + + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call) { + exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | + revFlow(arg, state, returnCtx, returnAp, ap) and + revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap) + ) + } + + additional predicate stats( + boolean fwd, int nodes, int fields, int conscand, int states, int tuples + ) { + fwd = true and + nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap)) and + states = count(FlowState state | fwdFlow(_, state, _, _, _, _)) and + tuples = + count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | + fwdFlow(n, state, cc, summaryCtx, argAp, ap) + ) + or + fwd = false and + nodes = count(NodeEx node | revFlow(node, _, _, _, _)) and + fields = count(TypedContent f0 | consCand(f0, _)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap)) and + states = count(FlowState state | revFlow(_, state, _, _, _)) and + tuples = + count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | + revFlow(n, state, returnCtx, retAp, ap) + ) + } + /* End: Stage logic. */ + } + } + + private module BooleanCallContext { + class Cc extends boolean { + Cc() { this in [true, false] } + } + + class CcCall extends Cc { + CcCall() { this = true } + } + + /** Holds if the call context may be `call`. */ + predicate matchesCall(CcCall cc, DataFlowCall call) { any() } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccNone() { result = false } + + CcCall ccSomeCall() { result = true } - module NoLocalCallContext { class LocalCc = Unit; bindingset[node, cc] LocalCc getLocalCc(NodeEx node, Cc cc) { any() } bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSiteDispatch(call, c) - then result = TSpecificCall(call) - else result = TSomeCall() + CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c, innercc] + CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } + } + + private module Level1CallContext { + class Cc = CallContext; + + class CcCall = CallContextCall; + + pragma[inline] + predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } + + class CcNoCall = CallContextNoCall; + + Cc ccNone() { result instanceof CallContextAny } + + CcCall ccSomeCall() { result instanceof CallContextSomeCall } + + module NoLocalCallContext { + class LocalCc = Unit; + + bindingset[node, cc] + LocalCc getLocalCc(NodeEx node, Cc cc) { any() } + + bindingset[call, c, outercc] + CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + checkCallContextCall(outercc, call, c) and + if recordDataFlowCallSiteDispatch(call, c) + then result = TSpecificCall(call) + else result = TSomeCall() + } + } + + module LocalCallContext { + class LocalCc = LocalCallContext; + + bindingset[node, cc] + LocalCc getLocalCc(NodeEx node, Cc cc) { + result = + getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), + node.getEnclosingCallable()) + } + + bindingset[call, c, outercc] + CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + checkCallContextCall(outercc, call, c) and + if recordDataFlowCallSite(call, c) + then result = TSpecificCall(call) + else result = TSomeCall() + } + } + + bindingset[call, c, innercc] + CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { + checkCallContextReturn(innercc, c, call) and + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() } } - module LocalCallContext { - class LocalCc = LocalCallContext; + private module Stage2Param implements MkStage::StageParam { + private module PrevStage = Stage1; - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { - result = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - node.getEnclosingCallable()) + class Ap extends boolean { + Ap() { this in [true, false] } } - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + class ApNil extends Ap { + ApNil() { this = false } } + + bindingset[result, ap] + PrevStage::Ap getApprox(Ap ap) { any() } + + ApNil getApNil(NodeEx node) { Stage1::revFlow(node) and exists(result) } + + bindingset[tc, tail] + Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + class ApHeadContent = Unit; + + pragma[inline] + ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } + + ApHeadContent projectToHeadContent(Content c) { any() } + + class ApOption = BooleanOption; + + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + + import Level1CallContext + import NoLocalCallContext + + bindingset[node1, state1] + bindingset[node2, state2] + predicate localStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + ApNil ap, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2) and + state1 = state2 + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2) and + state1 = state2 + or + preservesValue = false and + additionalLocalStateStep(node1, state1, node2, state2) + ) and + exists(ap) and + exists(lcc) + } + + predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + predicate flowIntoCall = flowIntoCallNodeCand1/4; + + pragma[nomagic] + private predicate expectsContentCand(NodeEx node) { + exists(Content c | + PrevStage::revFlow(node) and + PrevStage::revFlowIsReadAndStored(c) and + expectsContentEx(node, c) + ) + } + + bindingset[node, state, ap] + predicate filter(NodeEx node, FlowState state, Ap ap) { + PrevStage::revFlowState(state) and + exists(ap) and + not stateBarrier(node, state) and + ( + notExpectsContent(node) + or + ap = true and + expectsContentCand(node) + ) + } + + bindingset[ap, contentType] + predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } } - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { - checkCallContextReturn(innercc, c, call) and - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() + private module Stage2 implements StageSig { + import MkStage::Stage } -} - -private module Stage2Param implements MkStage::StageParam { - private module PrevStage = Stage1; - - class Ap extends boolean { - Ap() { this in [true, false] } - } - - class ApNil extends Ap { - ApNil() { this = false } - } - - bindingset[result, ap] - PrevStage::Ap getApprox(Ap ap) { any() } - - ApNil getApNil(NodeEx node) { Stage1::revFlow(node, _) and exists(result) } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } - - class ApHeadContent = Unit; - - pragma[inline] - ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } - - ApHeadContent projectToHeadContent(Content c) { any() } - - class ApOption = BooleanOption; - - ApOption apNone() { result = TBooleanNone() } - - ApOption apSome(Ap ap) { result = TBooleanSome(ap) } - - import Level1CallContext - import NoLocalCallContext - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - ( - preservesValue = true and - localFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalStateStep(node1, state1, node2, state2, config) - ) and - exists(ap) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand1/6; - - predicate flowIntoCall = flowIntoCallNodeCand1/5; pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - expectsContentEx(node, c) - ) + private predicate flowOutOfCallNodeCand2( + DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow + ) { + flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow) and + Stage2::revFlow(node2) and + Stage2::revFlowAlias(node1) } - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - PrevStage::revFlowState(state, pragma[only_bind_into](config)) and - exists(ap) and - not stateBarrier(node, state, config) and - ( - notExpectsContent(node) - or - ap = true and - expectsContentCand(node, config) - ) + pragma[nomagic] + private predicate flowIntoCallNodeCand2( + DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow + ) { + flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow) and + Stage2::revFlow(node2) and + Stage2::revFlowAlias(node1) } - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage2 implements StageSig { - import MkStage::Stage -} - -pragma[nomagic] -private predicate flowOutOfCallNodeCand2( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -pragma[nomagic] -private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -private module LocalFlowBigStep { - /** - * A node where some checking is required, and hence the big-step relation - * is not allowed to step over. - */ - private class FlowCheckNode extends NodeEx { - FlowCheckNode() { - castNode(this.asNode()) or - clearsContentCached(this.asNode(), _) or - expectsContentCached(this.asNode(), _) + private module LocalFlowBigStep { + /** + * A node where some checking is required, and hence the big-step relation + * is not allowed to step over. + */ + private class FlowCheckNode extends NodeEx { + FlowCheckNode() { + castNode(this.asNode()) or + clearsContentCached(this.asNode(), _) or + expectsContentCached(this.asNode(), _) + } } - } - /** - * Holds if `node` can be the first node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowEntry(NodeEx node, FlowState state, Configuration config) { - Stage2::revFlow(node, state, config) and - ( - sourceNode(node, state, config) + /** + * Holds if `node` can be the first node in a maximal subsequence of local + * flow steps in a dataflow path. + */ + private predicate localFlowEntry(NodeEx node, FlowState state) { + Stage2::revFlow(node, state) and + ( + sourceNode(node, state) + or + jumpStepEx(_, node) + or + additionalJumpStep(_, node) + or + additionalJumpStateStep(_, _, node, state) + or + node instanceof ParamNodeEx + or + node.asNode() instanceof OutNodeExt + or + Stage2::storeStepCand(_, _, _, node, _) + or + Stage2::readStepCand(_, _, node) + or + node instanceof FlowCheckNode + or + exists(FlowState s | + additionalLocalStateStep(_, s, node, state) and + s != state + ) + ) + } + + /** + * Holds if `node` can be the last node in a maximal subsequence of local + * flow steps in a dataflow path. + */ + private predicate localFlowExit(NodeEx node, FlowState state) { + exists(NodeEx next | Stage2::revFlow(next, state) | + jumpStepEx(node, next) or + additionalJumpStep(node, next) or + flowIntoCallNodeCand2(_, node, next, _) or + flowOutOfCallNodeCand2(_, node, _, next, _) or + Stage2::storeStepCand(node, _, _, next, _) or + Stage2::readStepCand(node, _, next) + ) or - jumpStep(_, node, config) - or - additionalJumpStep(_, node, config) - or - additionalJumpStateStep(_, _, node, state, config) - or - node instanceof ParamNodeEx - or - node.asNode() instanceof OutNodeExt - or - Stage2::storeStepCand(_, _, _, node, _, config) - or - Stage2::readStepCand(_, _, node, config) - or - node instanceof FlowCheckNode - or - exists(FlowState s | - additionalLocalStateStep(_, s, node, state, config) and + exists(NodeEx next, FlowState s | Stage2::revFlow(next, s) | + additionalJumpStateStep(node, state, next, s) + or + additionalLocalStateStep(node, state, next, s) and s != state ) - ) - } - - /** - * Holds if `node` can be the last node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowExit(NodeEx node, FlowState state, Configuration config) { - exists(NodeEx next | Stage2::revFlow(next, state, config) | - jumpStep(node, next, config) or - additionalJumpStep(node, next, config) or - flowIntoCallNodeCand2(_, node, next, _, config) or - flowOutOfCallNodeCand2(_, node, _, next, _, config) or - Stage2::storeStepCand(node, _, _, next, _, config) or - Stage2::readStepCand(node, _, next, config) - ) - or - exists(NodeEx next, FlowState s | Stage2::revFlow(next, s, config) | - additionalJumpStateStep(node, state, next, s, config) or - additionalLocalStateStep(node, state, next, s, config) and - s != state - ) - or - Stage2::revFlow(node, state, config) and - node instanceof FlowCheckNode - or - sinkNode(node, state, config) - } + Stage2::revFlow(node, state) and + node instanceof FlowCheckNode + or + sinkNode(node, state) + } - pragma[noinline] - private predicate additionalLocalFlowStepNodeCand2( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, Configuration config - ) { - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 and - Stage2::revFlow(node1, pragma[only_bind_into](state1), false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false, - pragma[only_bind_into](config)) - or - additionalLocalStateStep(node1, state1, node2, state2, config) and - Stage2::revFlow(node1, state1, false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, state2, false, pragma[only_bind_into](config)) - } + pragma[noinline] + private predicate additionalLocalFlowStepNodeCand2( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2 + ) { + additionalLocalFlowStepNodeCand1(node1, node2) and + state1 = state2 and + Stage2::revFlow(node1, pragma[only_bind_into](state1), false) and + Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false) + or + additionalLocalStateStep(node1, state1, node2, state2) and + Stage2::revFlow(node1, state1, false) and + Stage2::revFlowAlias(node2, state2, false) + } - /** - * Holds if the local path from `node1` to `node2` is a prefix of a maximal - * subsequence of local flow steps in a dataflow path. - * - * This is the transitive closure of `[additional]localFlowStep` beginning - * at `localFlowEntry`. - */ - pragma[nomagic] - private predicate localFlowStepPlus( - NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, - Configuration config, LocalCallContext cc - ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and - ( - localFlowEntry(node1, pragma[only_bind_into](state), pragma[only_bind_into](config)) and + /** + * Holds if the local path from `node1` to `node2` is a prefix of a maximal + * subsequence of local flow steps in a dataflow path. + * + * This is the transitive closure of `[additional]localFlowStep` beginning + * at `localFlowEntry`. + */ + pragma[nomagic] + private predicate localFlowStepPlus( + NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, + LocalCallContext cc + ) { + not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and ( - localFlowStepNodeCand1(node1, node2, config) and - preservesValue = true and - t = node1.getDataFlowType() and // irrelevant dummy value - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) + localFlowEntry(node1, pragma[only_bind_into](state)) and + ( + localFlowStepNodeCand1(node1, node2) and + preservesValue = true and + t = node1.getDataFlowType() and // irrelevant dummy value + Stage2::revFlow(node2, pragma[only_bind_into](state)) + or + additionalLocalFlowStepNodeCand2(node1, state, node2, state) and + preservesValue = false and + t = node2.getDataFlowType() + ) and + node1 != node2 and + cc.relevantFor(node1.getEnclosingCallable()) and + not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) or - additionalLocalFlowStepNodeCand2(node1, state, node2, state, config) and - preservesValue = false and - t = node2.getDataFlowType() - ) and - node1 != node2 and - cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) - or - exists(NodeEx mid | - localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, - pragma[only_bind_into](config), cc) and - localFlowStepNodeCand1(mid, node2, config) and - not mid instanceof FlowCheckNode and - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) + exists(NodeEx mid | + localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, cc) and + localFlowStepNodeCand1(mid, node2) and + not mid instanceof FlowCheckNode and + Stage2::revFlow(node2, pragma[only_bind_into](state)) + ) + or + exists(NodeEx mid | + localFlowStepPlus(node1, state, mid, _, _, cc) and + additionalLocalFlowStepNodeCand2(mid, state, node2, state) and + not mid instanceof FlowCheckNode and + preservesValue = false and + t = node2.getDataFlowType() + ) ) + } + + /** + * Holds if `node1` can step to `node2` in one or more local steps and this + * path can occur as a maximal subsequence of local steps in a dataflow path. + */ + pragma[nomagic] + predicate localFlowBigStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + DataFlowType t, LocalCallContext callContext + ) { + localFlowStepPlus(node1, state1, node2, preservesValue, t, callContext) and + localFlowExit(node2, state1) and + state1 = state2 or - exists(NodeEx mid | - localFlowStepPlus(node1, state, mid, _, _, pragma[only_bind_into](config), cc) and - additionalLocalFlowStepNodeCand2(mid, state, node2, state, config) and - not mid instanceof FlowCheckNode and - preservesValue = false and - t = node2.getDataFlowType() + additionalLocalFlowStepNodeCand2(node1, state1, node2, state2) and + state1 != state2 and + preservesValue = false and + t = node2.getDataFlowType() and + callContext.relevantFor(node1.getEnclosingCallable()) and + not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | + isUnreachableInCallCached(node1.asNode(), call) or + isUnreachableInCallCached(node2.asNode(), call) ) + } + } + + private import LocalFlowBigStep + + private module Stage3Param implements MkStage::StageParam { + private module PrevStage = Stage2; + + class Ap = ApproxAccessPathFront; + + class ApNil = ApproxAccessPathFrontNil; + + PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + ApNil getApNil(NodeEx node) { + PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) + } + + bindingset[tc, tail] + Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } + + class ApHeadContent = ContentApprox; + + pragma[noinline] + ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + predicate projectToHeadContent = getContentApprox/1; + + class ApOption = ApproxAccessPathFrontOption; + + ApOption apNone() { result = TApproxAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } + + import BooleanCallContext + + predicate localStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + ApproxAccessPathFrontNil ap, LocalCc lcc + ) { + localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), _) and + exists(lcc) + } + + predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + predicate flowIntoCall = flowIntoCallNodeCand2/4; + + pragma[nomagic] + private predicate expectsContentCand(NodeEx node, Ap ap) { + exists(Content c | + PrevStage::revFlow(node) and + PrevStage::readStepCand(_, c, _) and + expectsContentEx(node, c) and + c = ap.getAHead().getContent() + ) + } + + pragma[nomagic] + private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } + + bindingset[node, state, ap] + predicate filter(NodeEx node, FlowState state, Ap ap) { + exists(state) and + (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and + ( + notExpectsContent(node) + or + expectsContentCand(node, ap) + ) + } + + bindingset[ap, contentType] + predicate typecheckStore(Ap ap, DataFlowType contentType) { + // We need to typecheck stores here, since reverse flow through a getter + // might have a different type here compared to inside the getter. + compatibleTypes(ap.getType(), contentType) + } + } + + private module Stage3 implements StageSig { + import MkStage::Stage + } + + private module Stage4Param implements MkStage::StageParam { + private module PrevStage = Stage3; + + class Ap = AccessPathFront; + + class ApNil = AccessPathFrontNil; + + PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } + + ApNil getApNil(NodeEx node) { + PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) + } + + bindingset[tc, tail] + Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + class ApHeadContent = Content; + + pragma[noinline] + ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + ApHeadContent projectToHeadContent(Content c) { result = c } + + class ApOption = AccessPathFrontOption; + + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + + import BooleanCallContext + + pragma[nomagic] + predicate localStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + ApNil ap, LocalCc lcc + ) { + localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), _) and + PrevStage::revFlow(node1, pragma[only_bind_into](state1), _) and + PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _) and + exists(lcc) + } + + pragma[nomagic] + predicate flowOutOfCall( + DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow + ) { + exists(FlowState state | + flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow) and + PrevStage::revFlow(node2, pragma[only_bind_into](state), _) and + PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _) + ) + } + + pragma[nomagic] + predicate flowIntoCall( + DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow + ) { + exists(FlowState state | + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow) and + PrevStage::revFlow(node2, pragma[only_bind_into](state), _) and + PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _) + ) + } + + pragma[nomagic] + private predicate clearSet(NodeEx node, ContentSet c) { + PrevStage::revFlow(node) and + clearsContentCached(node.asNode(), c) + } + + pragma[nomagic] + private predicate clearContent(NodeEx node, Content c) { + exists(ContentSet cs | + PrevStage::readStepCand(_, pragma[only_bind_into](c), _) and + c = cs.getAReadContent() and + clearSet(node, cs) + ) + } + + pragma[nomagic] + private predicate clear(NodeEx node, Ap ap) { clearContent(node, ap.getHead().getContent()) } + + pragma[nomagic] + private predicate expectsContentCand(NodeEx node, Ap ap) { + exists(Content c | + PrevStage::revFlow(node) and + PrevStage::readStepCand(_, c, _) and + expectsContentEx(node, c) and + c = ap.getHead().getContent() + ) + } + + pragma[nomagic] + private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } + + bindingset[node, state, ap] + predicate filter(NodeEx node, FlowState state, Ap ap) { + exists(state) and + not clear(node, ap) and + (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and + ( + notExpectsContent(node) + or + expectsContentCand(node, ap) + ) + } + + bindingset[ap, contentType] + predicate typecheckStore(Ap ap, DataFlowType contentType) { + // We need to typecheck stores here, since reverse flow through a getter + // might have a different type here compared to inside the getter. + compatibleTypes(ap.getType(), contentType) + } + } + + private module Stage4 implements StageSig { + import MkStage::Stage + } + + /** + * Holds if `argApf` is recorded as the summary context for flow reaching `node` + * and remains relevant for the following pruning stage. + */ + private predicate flowCandSummaryCtx(NodeEx node, FlowState state, AccessPathFront argApf) { + exists(AccessPathFront apf | + Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf) and + Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf) ) } /** - * Holds if `node1` can step to `node2` in one or more local steps and this - * path can occur as a maximal subsequence of local steps in a dataflow path. + * Holds if a length 2 access path approximation with the head `tc` is expected + * to be expensive. */ - pragma[nomagic] - predicate localFlowBigStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - DataFlowType t, Configuration config, LocalCallContext callContext - ) { - localFlowStepPlus(node1, state1, node2, preservesValue, t, config, callContext) and - localFlowExit(node2, state1, config) and - state1 = state2 - or - additionalLocalFlowStepNodeCand2(node1, state1, node2, state2, config) and - state1 != state2 and - preservesValue = false and - t = node2.getDataFlowType() and - callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) - } -} - -private import LocalFlowBigStep - -private module Stage3Param implements MkStage::StageParam { - private module PrevStage = Stage2; - - class Ap = ApproxAccessPathFront; - - class ApNil = ApproxAccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } - - class ApHeadContent = ContentApprox; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - predicate projectToHeadContent = getContentApprox/1; - - class ApOption = ApproxAccessPathFrontOption; - - ApOption apNone() { result = TApproxAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } - - import BooleanCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApproxAccessPathFrontNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand2/6; - - predicate flowIntoCall = flowIntoCallNodeCand2/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getAHead().getContent() + private predicate expensiveLen2unfolding(TypedContent tc) { + exists(int tails, int nodes, int apLimit, int tupleLimit | + tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf)) and + nodes = + strictcount(NodeEx n, FlowState state | + Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc)) + or + flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc)) + ) and + accessPathApproxCostLimits(apLimit, tupleLimit) and + apLimit < tails and + tupleLimit < (tails - 1) * nodes and + not tc.forceHighPrecision() ) } - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } + private newtype TAccessPathApprox = + TNil(DataFlowType t) or + TConsNil(TypedContent tc, DataFlowType t) { + Stage4::consCand(tc, TFrontNil(t)) and + not expensiveLen2unfolding(tc) + } or + TConsCons(TypedContent tc1, TypedContent tc2, int len) { + Stage4::consCand(tc1, TFrontHead(tc2)) and + len in [2 .. accessPathLimit()] and + not expensiveLen2unfolding(tc1) + } or + TCons1(TypedContent tc, int len) { + len in [1 .. accessPathLimit()] and + expensiveLen2unfolding(tc) + } - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) + /** + * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only + * the first two elements of the list and its length are tracked. If data flows + * from a source to a given node with a given `AccessPathApprox`, this indicates + * the sequence of dereference operations needed to get from the value in the node + * to the tracked object. The final type indicates the type of the tracked object. + */ + abstract private class AccessPathApprox extends TAccessPathApprox { + abstract string toString(); + + abstract TypedContent getHead(); + + abstract int len(); + + abstract DataFlowType getType(); + + abstract AccessPathFront getFront(); + + /** Gets the access path obtained by popping `head` from this path, if any. */ + abstract AccessPathApprox pop(TypedContent head); } - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} + private class AccessPathApproxNil extends AccessPathApprox, TNil { + private DataFlowType t; -private module Stage3 implements StageSig { - import MkStage::Stage -} + AccessPathApproxNil() { this = TNil(t) } -private module Stage4Param implements MkStage::StageParam { - private module PrevStage = Stage3; + override string toString() { result = concat(": " + ppReprType(t)) } - class Ap = AccessPathFront; + override TypedContent getHead() { none() } - class ApNil = AccessPathFrontNil; + override int len() { result = 0 } - PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } + override DataFlowType getType() { result = t } - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) + override AccessPathFront getFront() { result = TFrontNil(t) } + + override AccessPathApprox pop(TypedContent head) { none() } } - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + abstract private class AccessPathApproxCons extends AccessPathApprox { } - class ApHeadContent = Content; + private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { + private TypedContent tc; + private DataFlowType t; - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } + AccessPathApproxConsNil() { this = TConsNil(tc, t) } - ApHeadContent projectToHeadContent(Content c) { result = c } + override string toString() { + // The `concat` becomes "" if `ppReprType` has no result. + result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) + } - class ApOption = AccessPathFrontOption; + override TypedContent getHead() { result = tc } - ApOption apNone() { result = TAccessPathFrontNone() } + override int len() { result = 1 } - ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + override DataFlowType getType() { result = tc.getContainerType() } - import BooleanCallContext + override AccessPathFront getFront() { result = TFrontHead(tc) } - pragma[nomagic] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) and - exists(lcc) + override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } } - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } + private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { + private TypedContent tc1; + private TypedContent tc2; + private int len; - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } + AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - pragma[nomagic] - private predicate clearSet(NodeEx node, ContentSet c, Configuration config) { - PrevStage::revFlow(node, config) and - clearsContentCached(node.asNode(), c) - } + override string toString() { + if len = 2 + then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" + else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" + } - pragma[nomagic] - private predicate clearContent(NodeEx node, Content c, Configuration config) { - exists(ContentSet cs | - PrevStage::readStepCand(_, pragma[only_bind_into](c), _, pragma[only_bind_into](config)) and - c = cs.getAReadContent() and - clearSet(node, cs, pragma[only_bind_into](config)) - ) - } + override TypedContent getHead() { result = tc1 } - pragma[nomagic] - private predicate clear(NodeEx node, Ap ap, Configuration config) { - clearContent(node, ap.getHead().getContent(), config) - } + override int len() { result = len } - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getHead().getContent() - ) - } + override DataFlowType getType() { result = tc1.getContainerType() } - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } + override AccessPathFront getFront() { result = TFrontHead(tc1) } - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - not clear(node, ap, config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage4 implements StageSig { - import MkStage::Stage -} - -/** - * Holds if `argApf` is recorded as the summary context for flow reaching `node` - * and remains relevant for the following pruning stage. - */ -private predicate flowCandSummaryCtx( - NodeEx node, FlowState state, AccessPathFront argApf, Configuration config -) { - exists(AccessPathFront apf | - Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf, config) and - Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf, - config) - ) -} - -/** - * Holds if a length 2 access path approximation with the head `tc` is expected - * to be expensive. - */ -private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { - exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf, config)) and - nodes = - strictcount(NodeEx n, FlowState state | - Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - or - flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - ) and - accessPathApproxCostLimits(apLimit, tupleLimit) and - apLimit < tails and - tupleLimit < (tails - 1) * nodes and - not tc.forceHighPrecision() - ) -} - -private newtype TAccessPathApprox = - TNil(DataFlowType t) or - TConsNil(TypedContent tc, DataFlowType t) { - Stage4::consCand(tc, TFrontNil(t), _) and - not expensiveLen2unfolding(tc, _) - } or - TConsCons(TypedContent tc1, TypedContent tc2, int len) { - Stage4::consCand(tc1, TFrontHead(tc2), _) and - len in [2 .. accessPathLimit()] and - not expensiveLen2unfolding(tc1, _) - } or - TCons1(TypedContent tc, int len) { - len in [1 .. accessPathLimit()] and - expensiveLen2unfolding(tc, _) - } - -/** - * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only - * the first two elements of the list and its length are tracked. If data flows - * from a source to a given node with a given `AccessPathApprox`, this indicates - * the sequence of dereference operations needed to get from the value in the node - * to the tracked object. The final type indicates the type of the tracked object. - */ -abstract private class AccessPathApprox extends TAccessPathApprox { - abstract string toString(); - - abstract TypedContent getHead(); - - abstract int len(); - - abstract DataFlowType getType(); - - abstract AccessPathFront getFront(); - - /** Gets the access path obtained by popping `head` from this path, if any. */ - abstract AccessPathApprox pop(TypedContent head); -} - -private class AccessPathApproxNil extends AccessPathApprox, TNil { - private DataFlowType t; - - AccessPathApproxNil() { this = TNil(t) } - - override string toString() { result = concat(": " + ppReprType(t)) } - - override TypedContent getHead() { none() } - - override int len() { result = 0 } - - override DataFlowType getType() { result = t } - - override AccessPathFront getFront() { result = TFrontNil(t) } - - override AccessPathApprox pop(TypedContent head) { none() } -} - -abstract private class AccessPathApproxCons extends AccessPathApprox { } - -private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { - private TypedContent tc; - private DataFlowType t; - - AccessPathApproxConsNil() { this = TConsNil(tc, t) } - - override string toString() { - // The `concat` becomes "" if `ppReprType` has no result. - result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) - } - - override TypedContent getHead() { result = tc } - - override int len() { result = 1 } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } -} - -private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { - private TypedContent tc1; - private TypedContent tc2; - private int len; - - AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - - override string toString() { - if len = 2 - then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" - else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc1 } - - override int len() { result = len } - - override DataFlowType getType() { result = tc1.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc1) } - - override AccessPathApprox pop(TypedContent head) { - head = tc1 and - ( - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - } -} - -private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { - private TypedContent tc; - private int len; - - AccessPathApproxCons1() { this = TCons1(tc, len) } - - override string toString() { - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc } - - override int len() { result = len } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { - head = tc and - ( - exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2), _) | + override AccessPathApprox pop(TypedContent head) { + head = tc1 and + ( result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -2763,1561 +2471,585 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or result = TCons1(tc2, len - 1) ) - or - exists(DataFlowType t | - len = 1 and - Stage4::consCand(tc, TFrontNil(t), _) and - result = TNil(t) + } + } + + private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { + private TypedContent tc; + private int len; + + AccessPathApproxCons1() { this = TCons1(tc, len) } + + override string toString() { + if len = 1 + then result = "[" + tc.toString() + "]" + else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" + } + + override TypedContent getHead() { result = tc } + + override int len() { result = len } + + override DataFlowType getType() { result = tc.getContainerType() } + + override AccessPathFront getFront() { result = TFrontHead(tc) } + + override AccessPathApprox pop(TypedContent head) { + head = tc and + ( + exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2)) | + result = TConsCons(tc2, _, len - 1) + or + len = 2 and + result = TConsNil(tc2, _) + or + result = TCons1(tc2, len - 1) + ) + or + exists(DataFlowType t | + len = 1 and + Stage4::consCand(tc, TFrontNil(t)) and + result = TNil(t) + ) ) - ) + } } -} -/** Gets the access path obtained by popping `tc` from `ap`, if any. */ -private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } + /** Gets the access path obtained by popping `tc` from `ap`, if any. */ + private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } -/** Gets the access path obtained by pushing `tc` onto `ap`. */ -private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } + /** Gets the access path obtained by pushing `tc` onto `ap`. */ + private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } -private newtype TAccessPathApproxOption = - TAccessPathApproxNone() or - TAccessPathApproxSome(AccessPathApprox apa) + private newtype TAccessPathApproxOption = + TAccessPathApproxNone() or + TAccessPathApproxSome(AccessPathApprox apa) -private class AccessPathApproxOption extends TAccessPathApproxOption { - string toString() { - this = TAccessPathApproxNone() and result = "" - or - this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) + private class AccessPathApproxOption extends TAccessPathApproxOption { + string toString() { + this = TAccessPathApproxNone() and result = "" + or + this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) + } } -} -private module Stage5Param implements MkStage::StageParam { - private module PrevStage = Stage4; + private module Stage5Param implements MkStage::StageParam { + private module PrevStage = Stage4; - class Ap = AccessPathApprox; + class Ap = AccessPathApprox; - class ApNil = AccessPathApproxNil; + class ApNil = AccessPathApproxNil; + + pragma[nomagic] + PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } + + ApNil getApNil(NodeEx node) { + PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) + } + + bindingset[tc, tail] + Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + class ApHeadContent = Content; + + pragma[noinline] + ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + ApHeadContent projectToHeadContent(Content c) { result = c } + + class ApOption = AccessPathApproxOption; + + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + + import Level1CallContext + import LocalCallContext + + predicate localStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + ApNil ap, LocalCc lcc + ) { + localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), lcc) and + PrevStage::revFlow(node1, pragma[only_bind_into](state1), _) and + PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _) + } + + pragma[nomagic] + predicate flowOutOfCall( + DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow + ) { + exists(FlowState state | + flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow) and + PrevStage::revFlow(node2, pragma[only_bind_into](state), _) and + PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _) + ) + } + + pragma[nomagic] + predicate flowIntoCall( + DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow + ) { + exists(FlowState state | + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow) and + PrevStage::revFlow(node2, pragma[only_bind_into](state), _) and + PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _) + ) + } + + bindingset[node, state, ap] + predicate filter(NodeEx node, FlowState state, Ap ap) { any() } + + // Type checking is not necessary here as it has already been done in stage 3. + bindingset[ap, contentType] + predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + } + + private module Stage5 = MkStage::Stage; pragma[nomagic] - PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathApproxOption; - - ApOption apNone() { result = TAccessPathApproxNone() } - - ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } - - import Level1CallContext - import LocalCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc + private predicate nodeMayUseSummary0( + NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, lcc) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) + exists(AccessPathApprox apa0 | + Stage5::parameterMayFlowThrough(p, _) and + Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0) and + Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), + TAccessPathApproxSome(apa), apa0) ) } pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) + private predicate nodeMayUseSummary(NodeEx n, FlowState state, AccessPathApprox apa) { + exists(ParamNodeEx p | + Stage5::parameterMayFlowThrough(p, apa) and + nodeMayUseSummary0(n, p, state, apa) ) } - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { any() } + private newtype TSummaryCtx = + TSummaryCtxNone() or + TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { + Stage5::parameterMayFlowThrough(p, ap.getApprox()) and + Stage5::revFlow(p, state, _) + } - // Type checking is not necessary here as it has already been done in stage 3. - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} + /** + * A context for generating flow summaries. This represents flow entry through + * a specific parameter with an access path of a specific shape. + * + * Summaries are only created for parameters that may flow through. + */ + abstract private class SummaryCtx extends TSummaryCtx { + abstract string toString(); + } -private module Stage5 = MkStage::Stage; + /** A summary context from which no flow summary can be generated. */ + private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { + override string toString() { result = "" } + } -bindingset[conf, result] -private Configuration unbindConf(Configuration conf) { - exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c)) -} + /** A summary context from which a flow summary can be generated. */ + private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { + private ParamNodeEx p; + private FlowState s; + private AccessPath ap; -pragma[nomagic] -private predicate nodeMayUseSummary0( - NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(AccessPathApprox apa0 | - Stage5::parameterMayFlowThrough(p, _, _) and - Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0, config) and - Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), - TAccessPathApproxSome(apa), apa0, config) - ) -} + SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } -pragma[nomagic] -private predicate nodeMayUseSummary( - NodeEx n, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::parameterMayFlowThrough(p, apa, config) and - nodeMayUseSummary0(n, p, state, apa, config) - ) -} + ParameterPosition getParameterPos() { p.isParameterOf(_, result) } -private newtype TSummaryCtx = - TSummaryCtxNone() or - TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { - exists(Configuration config | - Stage5::parameterMayFlowThrough(p, ap.getApprox(), config) and - Stage5::revFlow(p, state, _, config) + ParamNodeEx getParamNode() { result = p } + + override string toString() { result = p + ": " + ap } + + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * Gets the number of length 2 access path approximations that correspond to `apa`. + */ + private int count1to2unfold(AccessPathApproxCons1 apa) { + exists(TypedContent tc, int len | + tc = apa.getHead() and + len = apa.len() and + result = + strictcount(AccessPathFront apf | + Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1)) + ) ) } -/** - * A context for generating flow summaries. This represents flow entry through - * a specific parameter with an access path of a specific shape. - * - * Summaries are only created for parameters that may flow through. - */ -abstract private class SummaryCtx extends TSummaryCtx { - abstract string toString(); -} - -/** A summary context from which no flow summary can be generated. */ -private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { - override string toString() { result = "" } -} - -/** A summary context from which a flow summary can be generated. */ -private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParamNodeEx p; - private FlowState s; - private AccessPath ap; - - SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } - - ParameterPosition getParameterPos() { p.isParameterOf(_, result) } - - ParamNodeEx getParamNode() { result = p } - - override string toString() { result = p + ": " + ap } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** - * Gets the number of length 2 access path approximations that correspond to `apa`. - */ -private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { - exists(TypedContent tc, int len | - tc = apa.getHead() and - len = apa.len() and + private int countNodesUsingAccessPath(AccessPathApprox apa) { result = - strictcount(AccessPathFront apf | - Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), - config) + strictcount(NodeEx n, FlowState state | + Stage5::revFlow(n, state, apa) or nodeMayUseSummary(n, state, apa) ) - ) -} + } -private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = - strictcount(NodeEx n, FlowState state | - Stage5::revFlow(n, state, apa, config) or nodeMayUseSummary(n, state, apa, config) - ) -} - -/** - * Holds if a length 2 access path approximation matching `apa` is expected - * to be expensive. - */ -private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = count1to2unfold(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - apLimit < aps and - tupleLimit < (aps - 1) * nodes - ) -} - -private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { - exists(TypedContent head | - apa.pop(head) = result and - Stage5::consCand(head, result, config) - ) -} - -/** - * Holds with `unfold = false` if a precise head-tail representation of `apa` is - * expected to be expensive. Holds with `unfold = true` otherwise. - */ -private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { - if apa.getHead().forceHighPrecision() - then unfold = true - else + /** + * Holds if a length 2 access path approximation matching `apa` is expected + * to be expensive. + */ + private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa) { exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = countPotentialAps(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and + aps = count1to2unfold(apa) and + nodes = countNodesUsingAccessPath(apa) and accessPathCostLimits(apLimit, tupleLimit) and - if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true - ) -} - -/** - * Gets the number of `AccessPath`s that correspond to `apa`. - */ -pragma[assume_small_delta] -private int countAps(AccessPathApprox apa, Configuration config) { - evalUnfold(apa, false, config) and - result = 1 and - (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) - or - evalUnfold(apa, false, config) and - result = count1to2unfold(apa, config) and - not expensiveLen1to2unfolding(apa, config) - or - evalUnfold(apa, true, config) and - result = countPotentialAps(apa, config) -} - -/** - * Gets the number of `AccessPath`s that would correspond to `apa` assuming - * that it is expanded to a precise head-tail representation. - */ -language[monotonicAggregates] -pragma[assume_small_delta] -private int countPotentialAps(AccessPathApprox apa, Configuration config) { - apa instanceof AccessPathApproxNil and result = 1 - or - result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) -} - -private newtype TAccessPath = - TAccessPathNil(DataFlowType t) or - TAccessPathCons(TypedContent head, AccessPath tail) { - exists(AccessPathApproxCons apa | - not evalUnfold(apa, false, _) and - head = apa.getHead() and - tail.getApprox() = getATail(apa, _) - ) - } or - TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - not expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head1 = apa.getHead() and - head2 = getATail(apa, _).getHead() - ) - } or - TAccessPathCons1(TypedContent head, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head = apa.getHead() + apLimit < aps and + tupleLimit < (aps - 1) * nodes ) } -private newtype TPathNode = - pragma[assume_small_delta] - TPathNodeMid( - NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config - ) { - // A PathNode is introduced by a source ... - Stage5::revFlow(node, state, config) and - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - or - // ... or a step from an existing PathNode to another node. - exists(PathNodeMid mid | - pathStep(mid, node, state, cc, sc, ap) and - pragma[only_bind_into](config) = mid.getConfiguration() and - Stage5::revFlow(node, state, ap.getApprox(), pragma[only_bind_into](config)) - ) - } or - TPathNodeSink(NodeEx node, FlowState state, Configuration config) { - exists(PathNodeMid sink | - sink.isAtSink() and - node = sink.getNodeEx() and - state = sink.getState() and - config = sink.getConfiguration() - ) - } or - TPathNodeSourceGroup(string sourceGroup, Configuration config) { - exists(PathNodeImpl source | - sourceGroup = source.getSourceGroup() and - config = source.getConfiguration() - ) - } or - TPathNodeSinkGroup(string sinkGroup, Configuration config) { - exists(PathNodeSink sink | - sinkGroup = sink.getSinkGroup() and - config = sink.getConfiguration() + private AccessPathApprox getATail(AccessPathApprox apa) { + exists(TypedContent head | + apa.pop(head) = result and + Stage5::consCand(head, result) ) } -/** - * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a - * source to a given node with a given `AccessPath`, this indicates the sequence - * of dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ -private class AccessPath extends TAccessPath { - /** Gets the head of this access path, if any. */ - abstract TypedContent getHead(); - - /** Gets the tail of this access path, if any. */ - abstract AccessPath getTail(); - - /** Gets the front of this access path. */ - abstract AccessPathFront getFront(); - - /** Gets the approximation of this access path. */ - abstract AccessPathApprox getApprox(); - - /** Gets the length of this access path. */ - abstract int length(); - - /** Gets a textual representation of this access path. */ - abstract string toString(); - - /** Gets the access path obtained by popping `tc` from this access path, if any. */ - final AccessPath pop(TypedContent tc) { - result = this.getTail() and - tc = this.getHead() - } - - /** Gets the access path obtained by pushing `tc` onto this access path. */ - final AccessPath push(TypedContent tc) { this = result.pop(tc) } -} - -private class AccessPathNil extends AccessPath, TAccessPathNil { - private DataFlowType t; - - AccessPathNil() { this = TAccessPathNil(t) } - - DataFlowType getType() { result = t } - - override TypedContent getHead() { none() } - - override AccessPath getTail() { none() } - - override AccessPathFrontNil getFront() { result = TFrontNil(t) } - - override AccessPathApproxNil getApprox() { result = TNil(t) } - - override int length() { result = 0 } - - override string toString() { result = concat(": " + ppReprType(t)) } -} - -private class AccessPathCons extends AccessPath, TAccessPathCons { - private TypedContent head; - private AccessPath tail; - - AccessPathCons() { this = TAccessPathCons(head, tail) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { result = tail } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - pragma[assume_small_delta] - override AccessPathApproxCons getApprox() { - result = TConsNil(head, tail.(AccessPathNil).getType()) - or - result = TConsCons(head, tail.getHead(), this.length()) - or - result = TCons1(head, this.length()) - } - - pragma[assume_small_delta] - override int length() { result = 1 + tail.length() } - - private string toStringImpl(boolean needsSuffix) { - exists(DataFlowType t | - tail = TAccessPathNil(t) and - needsSuffix = false and - result = head.toString() + "]" + concat(" : " + ppReprType(t)) - ) - or - result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) - or - exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | - result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true - or - result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false - ) - or - exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | - result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true - or - result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false - ) - } - - override string toString() { - result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" - or - result = "[" + this.toStringImpl(false) - } -} - -private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { - private TypedContent head1; - private TypedContent head2; - private int len; - - AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } - - override TypedContent getHead() { result = head1 } - - override AccessPath getTail() { - Stage5::consCand(head1, result.getApprox(), _) and - result.getHead() = head2 and - result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head1) } - - override AccessPathApproxCons getApprox() { - result = TConsCons(head1, head2, len) or - result = TCons1(head1, len) - } - - override int length() { result = len } - - override string toString() { - if len = 2 - then result = "[" + head1.toString() + ", " + head2.toString() + "]" + /** + * Holds with `unfold = false` if a precise head-tail representation of `apa` is + * expected to be expensive. Holds with `unfold = true` otherwise. + */ + private predicate evalUnfold(AccessPathApprox apa, boolean unfold) { + if apa.getHead().forceHighPrecision() + then unfold = true else - result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" - } -} - -private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { - private TypedContent head; - private int len; - - AccessPathCons1() { this = TAccessPathCons1(head, len) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { - Stage5::consCand(head, result.getApprox(), _) and result.length() = len - 1 + exists(int aps, int nodes, int apLimit, int tupleLimit | + aps = countPotentialAps(apa) and + nodes = countNodesUsingAccessPath(apa) and + accessPathCostLimits(apLimit, tupleLimit) and + if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true + ) } - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - - override int length() { result = len } - - override string toString() { - if len = 1 - then result = "[" + head.toString() + "]" - else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" - } -} - -abstract private class PathNodeImpl extends TPathNode { - /** Gets the `FlowState` of this node. */ - abstract FlowState getState(); - - /** Gets the associated configuration. */ - abstract Configuration getConfiguration(); - - /** Holds if this node is a source. */ - abstract predicate isSource(); - - abstract PathNodeImpl getASuccessorImpl(); - - private PathNodeImpl getASuccessorIfHidden() { - this.isHidden() and - result = this.getASuccessorImpl() - } - - pragma[nomagic] - private PathNodeImpl getANonHiddenSuccessor0() { - result = this.getASuccessorIfHidden*() and - not result.isHidden() - } - - final PathNodeImpl getANonHiddenSuccessor() { - result = this.getASuccessorImpl().getANonHiddenSuccessor0() and - not this.isHidden() - } - - abstract NodeEx getNodeEx(); - - predicate isHidden() { - not this.getConfiguration().includeHiddenNodes() and - ( - hiddenNode(this.getNodeEx().asNode()) and - not this.isSource() and - not this instanceof PathNodeSink - or - this.getNodeEx() instanceof TNodeImplicitRead - ) - } - - string getSourceGroup() { - this.isSource() and - this.getConfiguration().sourceGrouping(this.getNodeEx().asNode(), result) - } - - predicate isFlowSource() { - this.isSource() and not exists(this.getSourceGroup()) + /** + * Gets the number of `AccessPath`s that correspond to `apa`. + */ + pragma[assume_small_delta] + private int countAps(AccessPathApprox apa) { + evalUnfold(apa, false) and + result = 1 and + (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa)) or - this instanceof PathNodeSourceGroup - } - - predicate isFlowSink() { - this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or - this instanceof PathNodeSinkGroup - } - - private string ppAp() { - this instanceof PathNodeSink and result = "" + evalUnfold(apa, false) and + result = count1to2unfold(apa) and + not expensiveLen1to2unfolding(apa) or - exists(string s | s = this.(PathNodeMid).getAp().toString() | - if s = "" then result = "" else result = " " + s - ) + evalUnfold(apa, true) and + result = countPotentialAps(apa) } - private string ppCtx() { - this instanceof PathNodeSink and result = "" + /** + * Gets the number of `AccessPath`s that would correspond to `apa` assuming + * that it is expanded to a precise head-tail representation. + */ + language[monotonicAggregates] + pragma[assume_small_delta] + private int countPotentialAps(AccessPathApprox apa) { + apa instanceof AccessPathApproxNil and result = 1 or - result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + result = strictsum(AccessPathApprox tail | tail = getATail(apa) | countAps(tail)) } - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** Holds if `n` can reach a sink. */ -private predicate directReach(PathNodeImpl n) { - n instanceof PathNodeSink or - n instanceof PathNodeSinkGroup or - directReach(n.getANonHiddenSuccessor()) -} - -/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ -private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } - -/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { - n1.getANonHiddenSuccessor() = n2 and directReach(n2) -} - -private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) - -/** - * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. - * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. - */ -class PathNode instanceof PathNodeImpl { - PathNode() { reach(this) } - - /** Gets a textual representation of this element. */ - final string toString() { result = super.toString() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - final string toStringWithContext() { result = super.toStringWithContext() } - - /** - * 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/). - */ - final predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - /** Gets the underlying `Node`. */ - final Node getNode() { super.getNodeEx().projectToNode() = result } - - /** Gets the `FlowState` of this node. */ - final FlowState getState() { result = super.getState() } - - /** Gets the associated configuration. */ - final Configuration getConfiguration() { result = super.getConfiguration() } - - /** Gets a successor of this node, if any. */ - final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } - - /** Holds if this node is a source. */ - final predicate isSource() { super.isSource() } - - /** Holds if this node is a grouping of source nodes. */ - final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group, _) } - - /** Holds if this node is a grouping of sink nodes. */ - final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group, _) } -} - -/** - * Provides the query predicates needed to include a graph in a path-problem query. - */ -module PathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } - - /** Holds if `n` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode n, string key, string val) { - key = "semmle.label" and val = n.toString() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { - Subpaths::subpaths(arg, par, ret, out) - } -} - -/** - * An intermediate flow graph node. This is a triple consisting of a `Node`, - * a `CallContext`, and a `Configuration`. - */ -private class PathNodeMid extends PathNodeImpl, TPathNodeMid { - NodeEx node; - FlowState state; - CallContext cc; - SummaryCtx sc; - AccessPath ap; - Configuration config; - - PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - SummaryCtx getSummaryCtx() { result = sc } - - AccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - private PathNodeMid getSuccMid() { - pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx(), result.getAp()) and - result.getConfiguration() = unbindConf(this.getConfiguration()) - } - - override PathNodeImpl getASuccessorImpl() { - // an intermediate step to another intermediate node - result = this.getSuccMid() - or - // a final step to a sink - result = this.getSuccMid().projectToSink() - } - - override predicate isSource() { - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - } - - predicate isAtSink() { - sinkNode(node, state, config) and - ap instanceof AccessPathNil and - if hasSinkCallCtx(config) - then - // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` - // is exactly what we need to check. This also implies - // `sc instanceof SummaryCtxNone`. - // For `FeatureEqualSourceSinkCallContext` the initial call context was - // set to `CallContextSomeCall` and jumps are disallowed, so - // `cc instanceof CallContextNoCall` never holds. On the other hand, - // in this case there's never any need to enter a call except to identify - // a summary, so the condition in `pathIntoCallable` enforces this, which - // means that `sc instanceof SummaryCtxNone` holds if and only if we are - // in the call context of the source. - sc instanceof SummaryCtxNone or - cc instanceof CallContextNoCall - else any() - } - - PathNodeSink projectToSink() { - this.isAtSink() and - result.getNodeEx() = node and - result.getState() = state and - result.getConfiguration() = unbindConf(config) - } -} - -/** - * A flow graph node corresponding to a sink. This is disjoint from the - * intermediate nodes in order to uniquely correspond to a given sink by - * excluding the `CallContext`. - */ -private class PathNodeSink extends PathNodeImpl, TPathNodeSink { - NodeEx node; - FlowState state; - Configuration config; - - PathNodeSink() { this = TPathNodeSink(node, state, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result = TPathNodeSinkGroup(this.getSinkGroup(), config) - } - - override predicate isSource() { sourceNode(node, state, config) } - - string getSinkGroup() { config.sinkGrouping(node.asNode(), result) } -} - -private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { - string sourceGroup; - Configuration config; - - PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result.getSourceGroup() = sourceGroup and - result.getConfiguration() = config - } - - override predicate isSource() { none() } - - override string toString() { result = sourceGroup } - - override 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 - } -} - -private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { - string sinkGroup; - Configuration config; - - PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { none() } - - override predicate isSource() { none() } - - override string toString() { result = sinkGroup } - - override 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 - } -} - -private predicate pathNode( - PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, - Configuration conf, LocalCallContext localCC -) { - midnode = mid.getNodeEx() and - state = mid.getState() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - midnode.getEnclosingCallable()) and - ap = mid.getAp() -} - -/** - * Holds if data may flow from `mid` to `node`. The last step in or out of - * a callable is recorded by `cc`. - */ -pragma[assume_small_delta] -pragma[nomagic] -private predicate pathStep( - PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap -) { - exists(NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC | - pathNode(mid, midnode, state0, cc, sc, ap, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, true, _, conf, localCC) - ) - or - exists( - AccessPath ap0, NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC - | - pathNode(mid, midnode, state0, cc, sc, ap0, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), conf, - localCC) and - ap0 instanceof AccessPathNil - ) - or - jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - ap = mid.getAp() - or - additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, mid.getConfiguration()) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - pathIntoCallable(mid, node, state, _, cc, sc, _, _) and ap = mid.getAp() - or - pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone - or - pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() -} - -pragma[nomagic] -private predicate pathReadStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - tc = ap0.getHead() and - Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -pragma[nomagic] -private predicate pathStoreStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -private predicate pathOutOfCallable0( - PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa, - Configuration config -) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - apa = mid.getAp().getApprox() and - config = mid.getConfiguration() -} - -pragma[nomagic] -private predicate pathOutOfCallable1( - PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPathApprox apa, Configuration config -) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - pathOutOfCallable0(mid, pos, state, innercc, apa, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) -} - -pragma[noinline] -private NodeEx getAnOutNodeFlow( - ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config -) { - result.asNode() = kind.getAnOutNode(call) and - Stage5::revFlow(result, _, apa, config) -} - -/** - * Holds if data may flow from `mid` to `out`. The last step of this path - * is a return from a callable and is recorded by `cc`, if needed. - */ -pragma[noinline] -private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { - exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | - pathOutOfCallable1(mid, call, kind, state, cc, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. - */ -pragma[noinline] -private predicate pathIntoArg( - PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(ArgNodeEx arg, ArgumentPosition apos | - pathNode(mid, arg, state, cc, _, ap, config, _) and - arg.asNode().(ArgNode).argumentOf(call, apos) and - apa = ap.getApprox() and - parameterMatch(ppos, apos) - ) -} - -pragma[nomagic] -private predicate parameterCand( - DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::revFlow(p, _, apa, config) and - p.isParameterOf(callable, pos) - ) -} - -pragma[nomagic] -private predicate pathIntoCallable0( - PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, AccessPath ap, Configuration config -) { - exists(AccessPathApprox apa | - pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - callable = resolveCall(call, outercc) and - parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa), - pragma[only_bind_into](config)) - ) -} - -/** - * Holds if data may flow from `mid` to `p` through `call`. The contexts - * before and after entering the callable are `outercc` and `innercc`, - * respectively. - */ -pragma[nomagic] -private predicate pathIntoCallable( - PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, - SummaryCtx sc, DataFlowCall call, Configuration config -) { - exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | - pathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - ( - sc = TSummaryCtxSome(p, state, ap) - or - not exists(TSummaryCtxSome(p, state, ap)) and - sc = TSummaryCtxNone() and - // When the call contexts of source and sink needs to match then there's - // never any reason to enter a callable except to find a summary. See also - // the comment in `PathNodeMid::isAtSink`. - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) -} - -/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ -pragma[nomagic] -private predicate paramFlowsThrough( - ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, - AccessPathApprox apa, Configuration config -) { - exists(RetNodeEx ret | - pathNode(_, ret, state, cc, sc, ap, config, _) and - kind = ret.getKind() and - apa = ap.getApprox() and - parameterFlowThroughAllowed(sc.getParamNode(), kind) - ) -} - -pragma[nomagic] -private predicate pathThroughCallable0( - DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(CallContext innercc, SummaryCtx sc | - pathIntoCallable(mid, _, _, cc, innercc, sc, call, config) and - paramFlowsThrough(kind, state, innercc, sc, ap, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` through a callable to the node `out`. - * The context `cc` is restored to its value prior to entering the callable. - */ -pragma[noinline] -private predicate pathThroughCallable( - PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap -) { - exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config | - pathThroughCallable0(call, mid, kind, state, cc, ap, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -private module Subpaths { - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths01( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - exists(Configuration config | - pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and - pathIntoCallable(arg, par, _, _, innercc, sc, _, config) and - paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, - pragma[only_bind_into](apout), _, unbindConf(config)) and - not arg.isHidden() - ) - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `sout`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths02( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and - out.asNode() = kind.getAnOutNode(_) - } - - pragma[nomagic] - private Configuration getPathNodeConf(PathNodeImpl n) { result = n.getConfiguration() } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple. - */ - pragma[nomagic] - private predicate subpaths03( - PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, AccessPath apout - ) { - exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | - subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and - pathNode(ret, retnode, sout, innercc, sc, apout, unbindConf(getPathNodeConf(arg)), _) and - kind = retnode.getKind() - ) - } - - private PathNodeImpl localStepToHidden(PathNodeImpl n) { - n.getASuccessorImpl() = result and - result.isHidden() and - exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | - localFlowBigStep(n1, _, n2, _, _, _, _, _) or - store(n1, _, n2, _, _) or - readSet(n1, _, n2, _) - ) - } - - pragma[nomagic] - private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { - succ = pred.getANonHiddenSuccessor() and - succNode = succ.getNodeEx() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { - exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | - pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and - subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and - hasSuccessor(pragma[only_bind_into](arg), par, p) and - not ret.isHidden() and - pathNode(out0, o, sout, _, _, apout, _, _) - | - out = out0 or out = out0.projectToSink() - ) - } - - /** - * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. - */ - predicate retReach(PathNodeImpl n) { - exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) - or - exists(PathNodeImpl mid | - retReach(mid) and - n.getANonHiddenSuccessor() = mid and - not subpaths(_, mid, _, _) - ) - } -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -private predicate hasFlowPath( - PathNodeImpl flowsource, PathNodeImpl flowsink, Configuration configuration -) { - flowsource.isFlowSource() and - flowsource.getConfiguration() = configuration and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.isFlowSink() -} - -private predicate flowsTo( - PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink, - Configuration configuration -) { - flowsource.isSource() and - flowsource.getConfiguration() = configuration and - flowsource.getNodeEx().asNode() = source and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.getNodeEx().asNode() = sink -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -predicate flowsTo(Node source, Node sink, Configuration configuration) { - flowsTo(_, _, source, sink, configuration) -} - -private predicate finalStats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples -) { - fwd = true and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and - tuples = count(PathNodeImpl pn) - or - fwd = false and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and - tuples = count(PathNode pn) -} - -/** - * INTERNAL: Only for debugging. - * - * Calculates per-stage metrics for data flow. - */ -predicate stageStats( - int n, string stage, int nodes, int fields, int conscand, int states, int tuples, - Configuration config -) { - stage = "1 Fwd" and - n = 10 and - Stage1::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "1 Rev" and - n = 15 and - Stage1::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Fwd" and - n = 20 and - Stage2::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Rev" and - n = 25 and - Stage2::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Fwd" and - n = 30 and - Stage3::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Rev" and - n = 35 and - Stage3::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Fwd" and - n = 40 and - Stage4::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Rev" and - n = 45 and - Stage4::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Fwd" and - n = 50 and - Stage5::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Rev" and - n = 55 and - Stage5::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) - or - stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) -} - -private module FlowExploration { - private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - jumpStep(node1, node2, config) - or - additionalJumpStep(node1, node2, config) - or - additionalJumpStateStep(node1, _, node2, _, config) - or - // flow into callable - viableParamArgEx(_, node2, node1) - or - // flow out of a callable - viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) - | - c1 = node1.getEnclosingCallable() and - c2 = node2.getEnclosingCallable() and - c1 != c2 - ) - } - - private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSource(n) or config.isSource(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSrc(mid, config) and callableStep(mid, c, config) - ) - } - - private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSink(n) or config.isSink(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSink(mid, config) and callableStep(c, mid, config) - ) - } - - private newtype TCallableExt = - TCallable(DataFlowCallable c, Configuration config) { - interestingCallableSrc(c, config) or - interestingCallableSink(c, config) + private newtype TAccessPath = + TAccessPathNil(DataFlowType t) or + TAccessPathCons(TypedContent head, AccessPath tail) { + exists(AccessPathApproxCons apa | + not evalUnfold(apa, false) and + head = apa.getHead() and + tail.getApprox() = getATail(apa) + ) } or - TCallableSrc() or - TCallableSink() + TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { + exists(AccessPathApproxCons apa | + evalUnfold(apa, false) and + not expensiveLen1to2unfolding(apa) and + apa.len() = len and + head1 = apa.getHead() and + head2 = getATail(apa).getHead() + ) + } or + TAccessPathCons1(TypedContent head, int len) { + exists(AccessPathApproxCons apa | + evalUnfold(apa, false) and + expensiveLen1to2unfolding(apa) and + apa.len() = len and + head = apa.getHead() + ) + } - private predicate callableExtSrc(TCallableSrc src) { any() } - - private predicate callableExtSink(TCallableSink sink) { any() } - - private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { - exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | - callableStep(c1, c2, config) and - ce1 = TCallable(c1, pragma[only_bind_into](config)) and - ce2 = TCallable(c2, pragma[only_bind_into](config)) - ) - or - exists(Node n, Configuration config | - ce1 = TCallableSrc() and - (config.isSource(n) or config.isSource(n, _)) and - ce2 = TCallable(getNodeEnclosingCallable(n), config) - ) - or - exists(Node n, Configuration config | - ce2 = TCallableSink() and - (config.isSink(n) or config.isSink(n, _)) and - ce1 = TCallable(getNodeEnclosingCallable(n), config) - ) - } - - private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { - callableExtStepFwd(ce2, ce1) - } - - private int distSrcExt(TCallableExt c) = - shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) - - private int distSinkExt(TCallableExt c) = - shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) - - private int distSrc(DataFlowCallable c, Configuration config) { - result = distSrcExt(TCallable(c, config)) - 1 - } - - private int distSink(DataFlowCallable c, Configuration config) { - result = distSinkExt(TCallable(c, config)) - 1 - } - - private newtype TPartialAccessPath = - TPartialNil(DataFlowType t) or - TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } + private newtype TPathNode = + pragma[assume_small_delta] + TPathNodeMid(NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap) { + // A PathNode is introduced by a source ... + Stage5::revFlow(node, state) and + sourceNode(node, state) and + ( + if hasSourceCallCtx() + then cc instanceof CallContextSomeCall + else cc instanceof CallContextAny + ) and + sc instanceof SummaryCtxNone and + ap = TAccessPathNil(node.getDataFlowType()) + or + // ... or a step from an existing PathNode to another node. + pathStep(_, node, state, cc, sc, ap) and + Stage5::revFlow(node, state, ap.getApprox()) + } or + TPathNodeSink(NodeEx node, FlowState state) { + exists(PathNodeMid sink | + sink.isAtSink() and + node = sink.getNodeEx() and + state = sink.getState() + ) + } or + TPathNodeSourceGroup(string sourceGroup) { + exists(PathNodeImpl source | sourceGroup = source.getSourceGroup()) + } or + TPathNodeSinkGroup(string sinkGroup) { + exists(PathNodeSink sink | sinkGroup = sink.getSinkGroup()) + } /** - * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first - * element of the list and its length are tracked. If data flows from a source to - * a given node with a given `AccessPath`, this indicates the sequence of - * dereference operations needed to get from the value in the node to the + * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a + * source to a given node with a given `AccessPath`, this indicates the sequence + * of dereference operations needed to get from the value in the node to the * tracked object. The final type indicates the type of the tracked object. */ - private class PartialAccessPath extends TPartialAccessPath { + private class AccessPath extends TAccessPath { + /** Gets the head of this access path, if any. */ + abstract TypedContent getHead(); + + /** Gets the tail of this access path, if any. */ + abstract AccessPath getTail(); + + /** Gets the front of this access path. */ + abstract AccessPathFront getFront(); + + /** Gets the approximation of this access path. */ + abstract AccessPathApprox getApprox(); + + /** Gets the length of this access path. */ + abstract int length(); + + /** Gets a textual representation of this access path. */ abstract string toString(); - TypedContent getHead() { this = TPartialCons(result, _) } - - int len() { - this = TPartialNil(_) and result = 0 - or - this = TPartialCons(_, result) + /** Gets the access path obtained by popping `tc` from this access path, if any. */ + final AccessPath pop(TypedContent tc) { + result = this.getTail() and + tc = this.getHead() } - DataFlowType getType() { - this = TPartialNil(result) - or - exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) - } + /** Gets the access path obtained by pushing `tc` onto this access path. */ + final AccessPath push(TypedContent tc) { this = result.pop(tc) } } - private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { - override string toString() { - exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) - } + private class AccessPathNil extends AccessPath, TAccessPathNil { + private DataFlowType t; + + AccessPathNil() { this = TAccessPathNil(t) } + + DataFlowType getType() { result = t } + + override TypedContent getHead() { none() } + + override AccessPath getTail() { none() } + + override AccessPathFrontNil getFront() { result = TFrontNil(t) } + + override AccessPathApproxNil getApprox() { result = TNil(t) } + + override int length() { result = 0 } + + override string toString() { result = concat(": " + ppReprType(t)) } } - private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { - override string toString() { - exists(TypedContent tc, int len | this = TPartialCons(tc, len) | - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" + private class AccessPathCons extends AccessPath, TAccessPathCons { + private TypedContent head; + private AccessPath tail; + + AccessPathCons() { this = TAccessPathCons(head, tail) } + + override TypedContent getHead() { result = head } + + override AccessPath getTail() { result = tail } + + override AccessPathFrontHead getFront() { result = TFrontHead(head) } + + pragma[assume_small_delta] + override AccessPathApproxCons getApprox() { + result = TConsNil(head, tail.(AccessPathNil).getType()) + or + result = TConsCons(head, tail.getHead(), this.length()) + or + result = TCons1(head, this.length()) + } + + pragma[assume_small_delta] + override int length() { result = 1 + tail.length() } + + private string toStringImpl(boolean needsSuffix) { + exists(DataFlowType t | + tail = TAccessPathNil(t) and + needsSuffix = false and + result = head.toString() + "]" + concat(" : " + ppReprType(t)) + ) + or + result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) + or + exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | + result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true + or + result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false + ) + or + exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | + result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true + or + result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false ) } - } - private newtype TRevPartialAccessPath = - TRevPartialNil() or - TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `Content`s, but only the first - * element of the list and its length are tracked. - */ - private class RevPartialAccessPath extends TRevPartialAccessPath { - abstract string toString(); - - Content getHead() { this = TRevPartialCons(result, _) } - - int len() { - this = TRevPartialNil() and result = 0 - or - this = TRevPartialCons(_, result) - } - } - - private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { - override string toString() { result = "" } - } - - private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { override string toString() { - exists(Content c, int len | this = TRevPartialCons(c, len) | - if len = 1 - then result = "[" + c.toString() + "]" - else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" - ) + result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" + or + result = "[" + this.toStringImpl(false) } } - private predicate relevantState(FlowState state) { - sourceNode(_, state, _) or - sinkNode(_, state, _) or - additionalLocalStateStep(_, state, _, _, _) or - additionalLocalStateStep(_, _, _, state, _) or - additionalJumpStateStep(_, state, _, _, _) or - additionalJumpStateStep(_, _, _, state, _) + private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { + private TypedContent head1; + private TypedContent head2; + private int len; + + AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } + + override TypedContent getHead() { result = head1 } + + override AccessPath getTail() { + Stage5::consCand(head1, result.getApprox()) and + result.getHead() = head2 and + result.length() = len - 1 + } + + override AccessPathFrontHead getFront() { result = TFrontHead(head1) } + + override AccessPathApproxCons getApprox() { + result = TConsCons(head1, head2, len) or + result = TCons1(head1, len) + } + + override int length() { result = len } + + override string toString() { + if len = 2 + then result = "[" + head1.toString() + ", " + head2.toString() + "]" + else + result = + "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" + } } - private newtype TSummaryCtx1 = - TSummaryCtx1None() or - TSummaryCtx1Param(ParamNodeEx p) + private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { + private TypedContent head; + private int len; - private newtype TSummaryCtx2 = - TSummaryCtx2None() or - TSummaryCtx2Some(FlowState s) { relevantState(s) } + AccessPathCons1() { this = TAccessPathCons1(head, len) } - private newtype TSummaryCtx3 = - TSummaryCtx3None() or - TSummaryCtx3Some(PartialAccessPath ap) + override TypedContent getHead() { result = head } - private newtype TRevSummaryCtx1 = - TRevSummaryCtx1None() or - TRevSummaryCtx1Some(ReturnPosition pos) + override AccessPath getTail() { + Stage5::consCand(head, result.getApprox()) and result.length() = len - 1 + } - private newtype TRevSummaryCtx2 = - TRevSummaryCtx2None() or - TRevSummaryCtx2Some(FlowState s) { relevantState(s) } + override AccessPathFrontHead getFront() { result = TFrontHead(head) } - private newtype TRevSummaryCtx3 = - TRevSummaryCtx3None() or - TRevSummaryCtx3Some(RevPartialAccessPath ap) + override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - private newtype TPartialPathNode = - TPartialPathNodeFwd( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = TPartialNil(node.getDataFlowType()) and - exists(config.explorationLimit()) - or - partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap, config) and - distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() - } or - TPartialPathNodeRev( - NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, - RevPartialAccessPath ap, Configuration config - ) { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() and - exists(config.explorationLimit()) - or - revPartialPathStep(_, node, state, sc1, sc2, sc3, ap, config) and - not clearsContentEx(node, ap.getHead()) and + override int length() { result = len } + + override string toString() { + if len = 1 + then result = "[" + head.toString() + "]" + else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" + } + } + + abstract private class PathNodeImpl extends TPathNode { + /** Gets the `FlowState` of this node. */ + abstract FlowState getState(); + + /** Holds if this node is a source. */ + abstract predicate isSource(); + + abstract PathNodeImpl getASuccessorImpl(); + + private PathNodeImpl getASuccessorIfHidden() { + this.isHidden() and + result = this.getASuccessorImpl() + } + + pragma[nomagic] + private PathNodeImpl getANonHiddenSuccessor0() { + result = this.getASuccessorIfHidden*() and + not result.isHidden() + } + + final PathNodeImpl getANonHiddenSuccessor() { + result = this.getASuccessorImpl().getANonHiddenSuccessor0() and + not this.isHidden() + } + + abstract NodeEx getNodeEx(); + + predicate isHidden() { + not Config::includeHiddenNodes() and ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead()) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() + hiddenNode(this.getNodeEx().asNode()) and + not this.isSource() and + not this instanceof PathNodeSink + or + this.getNodeEx() instanceof TNodeImplicitRead + ) } - pragma[nomagic] - private predicate partialPathNodeMk0( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap, config) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - not clearsContentEx(node, ap.getHead().getContent()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead().getContent()) - ) and - if node.asNode() instanceof CastingNode - then compatibleTypes(node.getDataFlowType(), ap.getType()) - else any() - } + string getSourceGroup() { + this.isSource() and + Config::sourceGrouping(this.getNodeEx().asNode(), result) + } + + predicate isFlowSource() { + this.isSource() and not exists(this.getSourceGroup()) + or + this instanceof PathNodeSourceGroup + } + + predicate isFlowSink() { + this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or + this instanceof PathNodeSinkGroup + } + + private string ppAp() { + this instanceof PathNodeSink and result = "" + or + exists(string s | s = this.(PathNodeMid).getAp().toString() | + if s = "" then result = "" else result = " " + s + ) + } + + private string ppCtx() { + this instanceof PathNodeSink and result = "" + or + result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + } - /** - * A `Node` augmented with a call context, an access path, and a configuration. - */ - class PartialPathNode extends TPartialPathNode { /** Gets a textual representation of this element. */ string toString() { result = this.getNodeEx().toString() + this.ppAp() } @@ -4341,305 +3073,339 @@ private module FlowExploration { ) { this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) } + } + + /** Holds if `n` can reach a sink. */ + private predicate directReach(PathNodeImpl n) { + n instanceof PathNodeSink or + n instanceof PathNodeSinkGroup or + directReach(n.getANonHiddenSuccessor()) + } + + /** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ + private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } + + /** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ + private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { + n1.getANonHiddenSuccessor() = n2 and directReach(n2) + } + + private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) + + /** + * A `Node` augmented with a call context (except for sinks) and an access path. + * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. + */ + class PathNode instanceof PathNodeImpl { + PathNode() { reach(this) } + + /** Gets a textual representation of this element. */ + final string toString() { result = super.toString() } + + /** + * Gets a textual representation of this element, including a textual + * representation of the call context. + */ + final string toStringWithContext() { result = super.toStringWithContext() } + + /** + * 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/). + */ + final predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } /** Gets the underlying `Node`. */ - final Node getNode() { this.getNodeEx().projectToNode() = result } + final Node getNode() { super.getNodeEx().projectToNode() = result } - FlowState getState() { none() } - - private NodeEx getNodeEx() { - result = this.(PartialPathNodeFwd).getNodeEx() or - result = this.(PartialPathNodeRev).getNodeEx() - } - - /** Gets the associated configuration. */ - Configuration getConfiguration() { none() } + /** Gets the `FlowState` of this node. */ + final FlowState getState() { result = super.getState() } /** Gets a successor of this node, if any. */ - PartialPathNode getASuccessor() { none() } + final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } - /** - * Gets the approximate distance to the nearest source measured in number - * of interprocedural steps. - */ - int getSourceDistance() { - result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } + /** Holds if this node is a source. */ + final predicate isSource() { super.isSource() } - /** - * Gets the approximate distance to the nearest sink measured in number - * of interprocedural steps. - */ - int getSinkDistance() { - result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } + /** Holds if this node is a grouping of source nodes. */ + final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group) } - private string ppAp() { - exists(string s | - s = this.(PartialPathNodeFwd).getAp().toString() or - s = this.(PartialPathNodeRev).getAp().toString() - | - if s = "" then result = "" else result = " " + s - ) - } - - private string ppCtx() { - result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" - } - - /** Holds if this is a source in a forward-flow path. */ - predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } - - /** Holds if this is a sink in a reverse-flow path. */ - predicate isRevSink() { this.(PartialPathNodeRev).isSink() } + /** Holds if this node is a grouping of sink nodes. */ + final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group) } } /** * Provides the query predicates needed to include a graph in a path-problem query. */ - module PartialPathGraph { + module PathGraph { /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } + query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } + + /** Holds if `n` is a node in the graph of data flow path explanations. */ + query predicate nodes(PathNode n, string key, string val) { + key = "semmle.label" and val = n.toString() + } + + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through + * a subpath between `par` and `ret` with the connecting edges `arg -> par` and + * `ret -> out` is summarized as the edge `arg -> out`. + */ + query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { + Subpaths::subpaths(arg, par, ret, out) + } } - private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { + /** + * An intermediate flow graph node. This is a tuple consisting of a `Node`, + * a `FlowState`, a `CallContext`, a `SummaryCtx`, and an `AccessPath`. + */ + private class PathNodeMid extends PathNodeImpl, TPathNodeMid { NodeEx node; FlowState state; CallContext cc; - TSummaryCtx1 sc1; - TSummaryCtx2 sc2; - TSummaryCtx3 sc3; - PartialAccessPath ap; - Configuration config; + SummaryCtx sc; + AccessPath ap; - PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap, config) } + PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap) } - NodeEx getNodeEx() { result = node } + override NodeEx getNodeEx() { result = node } override FlowState getState() { result = state } CallContext getCallContext() { result = cc } - TSummaryCtx1 getSummaryCtx1() { result = sc1 } + SummaryCtx getSummaryCtx() { result = sc } - TSummaryCtx2 getSummaryCtx2() { result = sc2 } + AccessPath getAp() { result = ap } - TSummaryCtx3 getSummaryCtx3() { result = sc3 } - - PartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeFwd getASuccessor() { - partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp(), - result.getConfiguration()) + private PathNodeMid getSuccMid() { + pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), + result.getSummaryCtx(), result.getAp()) } - predicate isSource() { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap instanceof TPartialNil + override PathNodeImpl getASuccessorImpl() { + // an intermediate step to another intermediate node + result = this.getSuccMid() + or + // a final step to a sink + result = this.getSuccMid().projectToSink() + } + + override predicate isSource() { + sourceNode(node, state) and + ( + if hasSourceCallCtx() + then cc instanceof CallContextSomeCall + else cc instanceof CallContextAny + ) and + sc instanceof SummaryCtxNone and + ap = TAccessPathNil(node.getDataFlowType()) + } + + predicate isAtSink() { + sinkNode(node, state) and + ap instanceof AccessPathNil and + if hasSinkCallCtx() + then + // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` + // is exactly what we need to check. This also implies + // `sc instanceof SummaryCtxNone`. + // For `FeatureEqualSourceSinkCallContext` the initial call context was + // set to `CallContextSomeCall` and jumps are disallowed, so + // `cc instanceof CallContextNoCall` never holds. On the other hand, + // in this case there's never any need to enter a call except to identify + // a summary, so the condition in `pathIntoCallable` enforces this, which + // means that `sc instanceof SummaryCtxNone` holds if and only if we are + // in the call context of the source. + sc instanceof SummaryCtxNone or + cc instanceof CallContextNoCall + else any() + } + + PathNodeSink projectToSink() { + this.isAtSink() and + result.getNodeEx() = node and + result.getState() = state } } - private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { + /** + * A flow graph node corresponding to a sink. This is disjoint from the + * intermediate nodes in order to uniquely correspond to a given sink by + * excluding the `CallContext`. + */ + private class PathNodeSink extends PathNodeImpl, TPathNodeSink { NodeEx node; FlowState state; - TRevSummaryCtx1 sc1; - TRevSummaryCtx2 sc2; - TRevSummaryCtx3 sc3; - RevPartialAccessPath ap; - Configuration config; - PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap, config) } + PathNodeSink() { this = TPathNodeSink(node, state) } - NodeEx getNodeEx() { result = node } + override NodeEx getNodeEx() { result = node } override FlowState getState() { result = state } - TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } + override PathNodeImpl getASuccessorImpl() { result = TPathNodeSinkGroup(this.getSinkGroup()) } - TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } + override predicate isSource() { sourceNode(node, state) } - TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } + string getSinkGroup() { Config::sinkGrouping(node.asNode(), result) } + } - RevPartialAccessPath getAp() { result = ap } + private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { + string sourceGroup; - override Configuration getConfiguration() { result = config } + PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup) } - override PartialPathNodeRev getASuccessor() { - revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), - this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp(), this.getConfiguration()) - } + override NodeEx getNodeEx() { none() } - predicate isSink() { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() + override FlowState getState() { none() } + + override PathNodeImpl getASuccessorImpl() { result.getSourceGroup() = sourceGroup } + + override predicate isSource() { none() } + + override string toString() { result = sourceGroup } + + override 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 } } - private predicate partialPathStep( - PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, - TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config + private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { + string sinkGroup; + + PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup) } + + override NodeEx getNodeEx() { none() } + + override FlowState getState() { none() } + + override PathNodeImpl getASuccessorImpl() { none() } + + override predicate isSource() { none() } + + override string toString() { result = sinkGroup } + + override 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 + } + } + + private predicate pathNode( + PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, + LocalCallContext localCC ) { - not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and - ( - localFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - ) - or - jumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - partialPathStoreStep(mid, _, _, node, ap) and + midnode = mid.getNodeEx() and state = mid.getState() and cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(PartialAccessPath ap0, TypedContent tc | - partialPathReadStep(mid, ap0, tc, node, cc, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsFwd(ap, tc, ap0, config) - ) - or - partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap, config) - or - partialPathOutOfCallable(mid, node, state, cc, ap, config) and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() - or - partialPathThroughCallable(mid, node, state, cc, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() + sc = mid.getSummaryCtx() and + localCC = + getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), + midnode.getEnclosingCallable()) and + ap = mid.getAp() } - bindingset[result, i] - private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } - - pragma[inline] - private predicate partialPathStoreStep( - PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, - PartialAccessPath ap2 + /** + * Holds if data may flow from `mid` to `node`. The last step in or out of + * a callable is recorded by `cc`. + */ + pragma[assume_small_delta] + pragma[nomagic] + private predicate pathStep( + PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap ) { - exists(NodeEx midNode, DataFlowType contentType | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - store(midNode, tc, node, contentType, mid.getConfiguration()) and - ap2.getHead() = tc and - ap2.len() = unbindInt(ap1.len() + 1) and - compatibleTypes(ap1.getType(), contentType) + exists(NodeEx midnode, FlowState state0, LocalCallContext localCC | + pathNode(mid, midnode, state0, cc, sc, ap, localCC) and + localFlowBigStep(midnode, state0, node, state, true, _, localCC) ) + or + exists(AccessPath ap0, NodeEx midnode, FlowState state0, LocalCallContext localCC | + pathNode(mid, midnode, state0, cc, sc, ap0, localCC) and + localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), localCC) and + ap0 instanceof AccessPathNil + ) + or + jumpStepEx(mid.getNodeEx(), node) and + state = mid.getState() and + cc instanceof CallContextAny and + sc instanceof SummaryCtxNone and + ap = mid.getAp() + or + additionalJumpStep(mid.getNodeEx(), node) and + state = mid.getState() and + cc instanceof CallContextAny and + sc instanceof SummaryCtxNone and + mid.getAp() instanceof AccessPathNil and + ap = TAccessPathNil(node.getDataFlowType()) + or + additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state) and + cc instanceof CallContextAny and + sc instanceof SummaryCtxNone and + mid.getAp() instanceof AccessPathNil and + ap = TAccessPathNil(node.getDataFlowType()) + or + exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and + sc = mid.getSummaryCtx() + or + exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and + sc = mid.getSummaryCtx() + or + pathIntoCallable(mid, node, state, _, cc, sc, _) and ap = mid.getAp() + or + pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone + or + pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() } pragma[nomagic] - private predicate apConsFwd( - PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config + private predicate pathReadStep( + PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc ) { - exists(PartialPathNodeFwd mid | - partialPathStoreStep(mid, ap1, tc, _, ap2) and - config = mid.getConfiguration() - ) + ap0 = mid.getAp() and + tc = ap0.getHead() and + Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node) and + state = mid.getState() and + cc = mid.getCallContext() } pragma[nomagic] - private predicate partialPathReadStep( - PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc, - Configuration config + private predicate pathStoreStep( + PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and - ap.getHead() = tc and - pragma[only_bind_into](config) = mid.getConfiguration() and - cc = mid.getCallContext() - ) + ap0 = mid.getAp() and + Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _) and + state = mid.getState() and + cc = mid.getCallContext() } - private predicate partialPathOutOfCallable0( - PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, - PartialAccessPath ap, Configuration config + private predicate pathOutOfCallable0( + PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa ) { pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and state = mid.getState() and innercc = mid.getCallContext() and innercc instanceof CallContextNoCall and - ap = mid.getAp() and - config = mid.getConfiguration() + apa = mid.getAp().getApprox() } pragma[nomagic] - private predicate partialPathOutOfCallable1( - PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config + private predicate pathOutOfCallable1( + PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, + AccessPathApprox apa ) { exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - partialPathOutOfCallable0(mid, pos, state, innercc, ap, config) and + pathOutOfCallable0(mid, pos, state, innercc, apa) and c = pos.getCallable() and kind = pos.getKind() and resolveReturn(innercc, c, call) @@ -4648,53 +3414,86 @@ private module FlowExploration { ) } - private predicate partialPathOutOfCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(ReturnKindExt kind, DataFlowCall call | - partialPathOutOfCallable1(mid, call, kind, state, cc, ap, config) - | - out.asNode() = kind.getAnOutNode(call) + pragma[noinline] + private NodeEx getAnOutNodeFlow(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa) { + result.asNode() = kind.getAnOutNode(call) and + Stage5::revFlow(result, _, apa) + } + + /** + * Holds if data may flow from `mid` to `out`. The last step of this path + * is a return from a callable and is recorded by `cc`, if needed. + */ + pragma[noinline] + private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { + exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa | + pathOutOfCallable1(mid, call, kind, state, cc, apa) and + out = getAnOutNodeFlow(kind, call, apa) ) } + /** + * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. + */ pragma[noinline] - private predicate partialPathIntoArg( - PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, - DataFlowCall call, PartialAccessPath ap, Configuration config + private predicate pathIntoArg( + PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, + AccessPath ap, AccessPathApprox apa ) { - exists(ArgNode arg, ArgumentPosition apos | - arg = mid.getNodeEx().asNode() and - state = mid.getState() and - cc = mid.getCallContext() and - arg.argumentOf(call, apos) and - ap = mid.getAp() and - config = mid.getConfiguration() and + exists(ArgNodeEx arg, ArgumentPosition apos | + pathNode(mid, arg, state, cc, _, ap, _) and + arg.asNode().(ArgNode).argumentOf(call, apos) and + apa = ap.getApprox() and parameterMatch(ppos, apos) ) } pragma[nomagic] - private predicate partialPathIntoCallable0( - PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, PartialAccessPath ap, Configuration config + private predicate parameterCand( + DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa ) { - partialPathIntoArg(mid, pos, state, outercc, call, ap, config) and - callable = resolveCall(call, outercc) + exists(ParamNodeEx p | + Stage5::revFlow(p, _, apa) and + p.isParameterOf(callable, pos) + ) } - private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, - CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, - DataFlowCall call, PartialAccessPath ap, Configuration config + pragma[nomagic] + private predicate pathIntoCallable0( + PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, + CallContext outercc, DataFlowCall call, AccessPath ap ) { - exists(ParameterPosition pos, DataFlowCallable callable | - partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and + exists(AccessPathApprox apa | + pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, + pragma[only_bind_into](apa)) and + callable = resolveCall(call, outercc) and + parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa)) + ) + } + + /** + * Holds if data may flow from `mid` to `p` through `call`. The contexts + * before and after entering the callable are `outercc` and `innercc`, + * respectively. + */ + pragma[nomagic] + private predicate pathIntoCallable( + PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, + SummaryCtx sc, DataFlowCall call + ) { + exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | + pathIntoCallable0(mid, callable, pos, state, outercc, call, ap) and p.isParameterOf(callable, pos) and - sc1 = TSummaryCtx1Param(p) and - sc2 = TSummaryCtx2Some(state) and - sc3 = TSummaryCtx3Some(ap) + ( + sc = TSummaryCtxSome(p, state, ap) + or + not exists(TSummaryCtxSome(p, state, ap)) and + sc = TSummaryCtxNone() and + // When the call contexts of source and sink needs to match then there's + // never any reason to enter a callable except to find a summary. See also + // the comment in `PathNodeMid::isAtSink`. + not Config::getAFeature() instanceof FeatureEqualSourceSinkCallContext + ) | if recordDataFlowCallSite(call, callable) then innercc = TSpecificCall(call) @@ -4702,252 +3501,1108 @@ private module FlowExploration { ) } + /** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ pragma[nomagic] - private predicate paramFlowsThroughInPartialPath( - ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config + private predicate paramFlowsThrough( + ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, + AccessPathApprox apa ) { - exists(PartialPathNodeFwd mid, RetNodeEx ret | - mid.getNodeEx() = ret and + exists(RetNodeEx ret | + pathNode(_, ret, state, cc, sc, ap, _) and kind = ret.getKind() and + apa = ap.getApprox() and + parameterFlowThroughAllowed(sc.getParamNode(), kind) + ) + } + + pragma[assume_small_delta] + pragma[nomagic] + private predicate pathThroughCallable0( + DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, + AccessPath ap, AccessPathApprox apa + ) { + exists(CallContext innercc, SummaryCtx sc | + pathIntoCallable(mid, _, _, cc, innercc, sc, call) and + paramFlowsThrough(kind, state, innercc, sc, ap, apa) + ) + } + + /** + * Holds if data may flow from `mid` through a callable to the node `out`. + * The context `cc` is restored to its value prior to entering the callable. + */ + pragma[noinline] + private predicate pathThroughCallable( + PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap + ) { + exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa | + pathThroughCallable0(call, mid, kind, state, cc, ap, apa) and + out = getAnOutNodeFlow(kind, call, apa) + ) + } + + private module Subpaths { + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by + * `kind`, `sc`, `apout`, and `innercc`. + */ + pragma[nomagic] + private predicate subpaths01( + PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, + NodeEx out, FlowState sout, AccessPath apout + ) { + pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and + pathIntoCallable(arg, par, _, _, innercc, sc, _) and + paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, + pragma[only_bind_into](apout), _) and + not arg.isHidden() + } + + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by + * `kind`, `sc`, `sout`, `apout`, and `innercc`. + */ + pragma[nomagic] + private predicate subpaths02( + PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, + NodeEx out, FlowState sout, AccessPath apout + ) { + subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and + out.asNode() = kind.getAnOutNode(_) + } + + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple. + */ + pragma[nomagic] + private predicate subpaths03( + PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, + AccessPath apout + ) { + exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | + subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and + pathNode(ret, retnode, sout, innercc, sc, apout, _) and + kind = retnode.getKind() + ) + } + + private PathNodeImpl localStepToHidden(PathNodeImpl n) { + n.getASuccessorImpl() = result and + result.isHidden() and + exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | + localFlowBigStep(n1, _, n2, _, _, _, _) or + storeEx(n1, _, n2, _) or + readSetEx(n1, _, n2) + ) + } + + pragma[nomagic] + private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { + succ = pred.getANonHiddenSuccessor() and + succNode = succ.getNodeEx() + } + + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through + * a subpath between `par` and `ret` with the connecting edges `arg -> par` and + * `ret -> out` is summarized as the edge `arg -> out`. + */ + predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { + exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | + pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and + subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and + hasSuccessor(pragma[only_bind_into](arg), par, p) and + not ret.isHidden() and + pathNode(out0, o, sout, _, _, apout, _) + | + out = out0 or out = out0.projectToSink() + ) + } + + /** + * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. + */ + predicate retReach(PathNodeImpl n) { + exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) + or + exists(PathNodeImpl mid | + retReach(mid) and + n.getANonHiddenSuccessor() = mid and + not subpaths(_, mid, _, _) + ) + } + } + + /** + * Holds if data can flow from `source` to `sink`. + * + * The corresponding paths are generated from the end-points and the graph + * included in the module `PathGraph`. + */ + predicate hasFlowPath(PathNode source, PathNode sink) { + exists(PathNodeImpl flowsource, PathNodeImpl flowsink | + source = flowsource and sink = flowsink + | + flowsource.isFlowSource() and + (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and + flowsink.isFlowSink() + ) + } + + private predicate flowsTo(PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink) { + flowsource.isSource() and + flowsource.getNodeEx().asNode() = source and + (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and + flowsink.getNodeEx().asNode() = sink + } + + /** + * Holds if data can flow from `source` to `sink`. + */ + predicate hasFlow(Node source, Node sink) { flowsTo(_, _, source, sink) } + + /** + * Holds if data can flow from some source to `sink`. + */ + predicate hasFlowTo(Node sink) { sink = any(PathNodeSink n).getNodeEx().asNode() } + + /** + * Holds if data can flow from some source to `sink`. + */ + predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) } + + private predicate finalStats( + boolean fwd, int nodes, int fields, int conscand, int states, int tuples + ) { + fwd = true and + nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and + fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and + conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and + states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and + tuples = count(PathNodeImpl pn) + or + fwd = false and + nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and + fields = + count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and + conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and + states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and + tuples = count(PathNode pn) + } + + /** + * INTERNAL: Only for debugging. + * + * Calculates per-stage metrics for data flow. + */ + predicate stageStats( + int n, string stage, int nodes, int fields, int conscand, int states, int tuples + ) { + stage = "1 Fwd" and + n = 10 and + Stage1::stats(true, nodes, fields, conscand, states, tuples) + or + stage = "1 Rev" and + n = 15 and + Stage1::stats(false, nodes, fields, conscand, states, tuples) + or + stage = "2 Fwd" and + n = 20 and + Stage2::stats(true, nodes, fields, conscand, states, tuples) + or + stage = "2 Rev" and + n = 25 and + Stage2::stats(false, nodes, fields, conscand, states, tuples) + or + stage = "3 Fwd" and + n = 30 and + Stage3::stats(true, nodes, fields, conscand, states, tuples) + or + stage = "3 Rev" and + n = 35 and + Stage3::stats(false, nodes, fields, conscand, states, tuples) + or + stage = "4 Fwd" and + n = 40 and + Stage4::stats(true, nodes, fields, conscand, states, tuples) + or + stage = "4 Rev" and + n = 45 and + Stage4::stats(false, nodes, fields, conscand, states, tuples) + or + stage = "5 Fwd" and + n = 50 and + Stage5::stats(true, nodes, fields, conscand, states, tuples) + or + stage = "5 Rev" and + n = 55 and + Stage5::stats(false, nodes, fields, conscand, states, tuples) + or + stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) + or + stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) + } + + module FlowExploration { + private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2) { + exists(NodeEx node1, NodeEx node2 | + jumpStepEx(node1, node2) + or + additionalJumpStep(node1, node2) + or + additionalJumpStateStep(node1, _, node2, _) + or + // flow into callable + viableParamArgEx(_, node2, node1) + or + // flow out of a callable + viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) + | + c1 = node1.getEnclosingCallable() and + c2 = node2.getEnclosingCallable() and + c1 != c2 + ) + } + + private predicate interestingCallableSrc(DataFlowCallable c) { + exists(Node n | Config::isSource(n, _) and c = getNodeEnclosingCallable(n)) + or + exists(DataFlowCallable mid | interestingCallableSrc(mid) and callableStep(mid, c)) + } + + private predicate interestingCallableSink(DataFlowCallable c) { + exists(Node n | Config::isSink(n, _) and c = getNodeEnclosingCallable(n)) + or + exists(DataFlowCallable mid | interestingCallableSink(mid) and callableStep(c, mid)) + } + + private newtype TCallableExt = + TCallable(DataFlowCallable c) { + interestingCallableSrc(c) or + interestingCallableSink(c) + } or + TCallableSrc() or + TCallableSink() + + private predicate callableExtSrc(TCallableSrc src) { any() } + + private predicate callableExtSink(TCallableSink sink) { any() } + + private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { + exists(DataFlowCallable c1, DataFlowCallable c2 | + callableStep(c1, c2) and + ce1 = TCallable(c1) and + ce2 = TCallable(c2) + ) + or + exists(Node n | + ce1 = TCallableSrc() and + Config::isSource(n, _) and + ce2 = TCallable(getNodeEnclosingCallable(n)) + ) + or + exists(Node n | + ce2 = TCallableSink() and + Config::isSink(n, _) and + ce1 = TCallable(getNodeEnclosingCallable(n)) + ) + } + + private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { + callableExtStepFwd(ce2, ce1) + } + + private int distSrcExt(TCallableExt c) = + shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) + + private int distSinkExt(TCallableExt c) = + shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) + + private int distSrc(DataFlowCallable c) { result = distSrcExt(TCallable(c)) - 1 } + + private int distSink(DataFlowCallable c) { result = distSinkExt(TCallable(c)) - 1 } + + private newtype TPartialAccessPath = + TPartialNil(DataFlowType t) or + TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } + + /** + * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first + * element of the list and its length are tracked. If data flows from a source to + * a given node with a given `AccessPath`, this indicates the sequence of + * dereference operations needed to get from the value in the node to the + * tracked object. The final type indicates the type of the tracked object. + */ + private class PartialAccessPath extends TPartialAccessPath { + abstract string toString(); + + TypedContent getHead() { this = TPartialCons(result, _) } + + int len() { + this = TPartialNil(_) and result = 0 + or + this = TPartialCons(_, result) + } + + DataFlowType getType() { + this = TPartialNil(result) + or + exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) + } + } + + private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { + override string toString() { + exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) + } + } + + private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { + override string toString() { + exists(TypedContent tc, int len | this = TPartialCons(tc, len) | + if len = 1 + then result = "[" + tc.toString() + "]" + else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" + ) + } + } + + private newtype TRevPartialAccessPath = + TRevPartialNil() or + TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } + + /** + * Conceptually a list of `Content`s, but only the first + * element of the list and its length are tracked. + */ + private class RevPartialAccessPath extends TRevPartialAccessPath { + abstract string toString(); + + Content getHead() { this = TRevPartialCons(result, _) } + + int len() { + this = TRevPartialNil() and result = 0 + or + this = TRevPartialCons(_, result) + } + } + + private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { + override string toString() { result = "" } + } + + private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { + override string toString() { + exists(Content c, int len | this = TRevPartialCons(c, len) | + if len = 1 + then result = "[" + c.toString() + "]" + else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" + ) + } + } + + private predicate relevantState(FlowState state) { + sourceNode(_, state) or + sinkNode(_, state) or + additionalLocalStateStep(_, state, _, _) or + additionalLocalStateStep(_, _, _, state) or + additionalJumpStateStep(_, state, _, _) or + additionalJumpStateStep(_, _, _, state) + } + + private newtype TSummaryCtx1 = + TSummaryCtx1None() or + TSummaryCtx1Param(ParamNodeEx p) + + private newtype TSummaryCtx2 = + TSummaryCtx2None() or + TSummaryCtx2Some(FlowState s) { relevantState(s) } + + private newtype TSummaryCtx3 = + TSummaryCtx3None() or + TSummaryCtx3Some(PartialAccessPath ap) + + private newtype TRevSummaryCtx1 = + TRevSummaryCtx1None() or + TRevSummaryCtx1Some(ReturnPosition pos) + + private newtype TRevSummaryCtx2 = + TRevSummaryCtx2None() or + TRevSummaryCtx2Some(FlowState s) { relevantState(s) } + + private newtype TRevSummaryCtx3 = + TRevSummaryCtx3None() or + TRevSummaryCtx3Some(RevPartialAccessPath ap) + + private newtype TPartialPathNode = + TPartialPathNodeFwd( + NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, + TSummaryCtx3 sc3, PartialAccessPath ap + ) { + sourceNode(node, state) and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() and + ap = TPartialNil(node.getDataFlowType()) and + exists(explorationLimit()) + or + partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap) and + distSrc(node.getEnclosingCallable()) <= explorationLimit() + } or + TPartialPathNodeRev( + NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, + RevPartialAccessPath ap + ) { + sinkNode(node, state) and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + sc3 = TRevSummaryCtx3None() and + ap = TRevPartialNil() and + exists(explorationLimit()) + or + revPartialPathStep(_, node, state, sc1, sc2, sc3, ap) and + not clearsContentEx(node, ap.getHead()) and + ( + notExpectsContent(node) or + expectsContentEx(node, ap.getHead()) + ) and + not fullBarrier(node) and + not stateBarrier(node, state) and + distSink(node.getEnclosingCallable()) <= explorationLimit() + } + + pragma[nomagic] + private predicate partialPathNodeMk0( + NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, + TSummaryCtx3 sc3, PartialAccessPath ap + ) { + partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap) and + not fullBarrier(node) and + not stateBarrier(node, state) and + not clearsContentEx(node, ap.getHead().getContent()) and + ( + notExpectsContent(node) or + expectsContentEx(node, ap.getHead().getContent()) + ) and + if node.asNode() instanceof CastingNode + then compatibleTypes(node.getDataFlowType(), ap.getType()) + else any() + } + + /** + * A `Node` augmented with a call context, an access path, and a configuration. + */ + class PartialPathNode extends TPartialPathNode { + /** Gets a textual representation of this element. */ + string toString() { result = this.getNodeEx().toString() + this.ppAp() } + + /** + * Gets a textual representation of this element, including a textual + * representation of the call context. + */ + string toStringWithContext() { + result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() + } + + /** + * 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 + ) { + this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + + /** Gets the underlying `Node`. */ + final Node getNode() { this.getNodeEx().projectToNode() = result } + + FlowState getState() { none() } + + private NodeEx getNodeEx() { + result = this.(PartialPathNodeFwd).getNodeEx() or + result = this.(PartialPathNodeRev).getNodeEx() + } + + /** Gets a successor of this node, if any. */ + PartialPathNode getASuccessor() { none() } + + /** + * Gets the approximate distance to the nearest source measured in number + * of interprocedural steps. + */ + int getSourceDistance() { result = distSrc(this.getNodeEx().getEnclosingCallable()) } + + /** + * Gets the approximate distance to the nearest sink measured in number + * of interprocedural steps. + */ + int getSinkDistance() { result = distSink(this.getNodeEx().getEnclosingCallable()) } + + private string ppAp() { + exists(string s | + s = this.(PartialPathNodeFwd).getAp().toString() or + s = this.(PartialPathNodeRev).getAp().toString() + | + if s = "" then result = "" else result = " " + s + ) + } + + private string ppCtx() { + result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" + } + + /** Holds if this is a source in a forward-flow path. */ + predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } + + /** Holds if this is a sink in a reverse-flow path. */ + predicate isRevSink() { this.(PartialPathNodeRev).isSink() } + } + + /** + * Provides the query predicates needed to include a graph in a path-problem query. + */ + module PartialPathGraph { + /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ + query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } + } + + private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { + NodeEx node; + FlowState state; + CallContext cc; + TSummaryCtx1 sc1; + TSummaryCtx2 sc2; + TSummaryCtx3 sc3; + PartialAccessPath ap; + + PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap) } + + NodeEx getNodeEx() { result = node } + + override FlowState getState() { result = state } + + CallContext getCallContext() { result = cc } + + TSummaryCtx1 getSummaryCtx1() { result = sc1 } + + TSummaryCtx2 getSummaryCtx2() { result = sc2 } + + TSummaryCtx3 getSummaryCtx3() { result = sc3 } + + PartialAccessPath getAp() { result = ap } + + override PartialPathNodeFwd getASuccessor() { + partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), + result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp()) + } + + predicate isSource() { + sourceNode(node, state) and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() and + ap instanceof TPartialNil + } + } + + private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { + NodeEx node; + FlowState state; + TRevSummaryCtx1 sc1; + TRevSummaryCtx2 sc2; + TRevSummaryCtx3 sc3; + RevPartialAccessPath ap; + + PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap) } + + NodeEx getNodeEx() { result = node } + + override FlowState getState() { result = state } + + TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } + + TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } + + TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } + + RevPartialAccessPath getAp() { result = ap } + + override PartialPathNodeRev getASuccessor() { + revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), + this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp()) + } + + predicate isSink() { + sinkNode(node, state) and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + sc3 = TRevSummaryCtx3None() and + ap = TRevPartialNil() + } + } + + private predicate partialPathStep( + PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, + TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap + ) { + not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and + ( + localFlowStepEx(mid.getNodeEx(), node) and + state = mid.getState() and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + ap = mid.getAp() + or + additionalLocalFlowStep(mid.getNodeEx(), node) and + state = mid.getState() and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + mid.getAp() instanceof PartialAccessPathNil and + ap = TPartialNil(node.getDataFlowType()) + or + additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state) and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + mid.getAp() instanceof PartialAccessPathNil and + ap = TPartialNil(node.getDataFlowType()) + ) + or + jumpStepEx(mid.getNodeEx(), node) and + state = mid.getState() and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() and + ap = mid.getAp() + or + additionalJumpStep(mid.getNodeEx(), node) and + state = mid.getState() and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() and + mid.getAp() instanceof PartialAccessPathNil and + ap = TPartialNil(node.getDataFlowType()) + or + additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state) and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() and + mid.getAp() instanceof PartialAccessPathNil and + ap = TPartialNil(node.getDataFlowType()) + or + partialPathStoreStep(mid, _, _, node, ap) and state = mid.getState() and cc = mid.getCallContext() and sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() and + sc3 = mid.getSummaryCtx3() + or + exists(PartialAccessPath ap0, TypedContent tc | + partialPathReadStep(mid, ap0, tc, node, cc) and + state = mid.getState() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + apConsFwd(ap, tc, ap0) + ) + or + partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap) + or + partialPathOutOfCallable(mid, node, state, cc, ap) and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() + or + partialPathThroughCallable(mid, node, state, cc, ap) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() + } + + bindingset[result, i] + private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } + + pragma[inline] + private predicate partialPathStoreStep( + PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, + PartialAccessPath ap2 + ) { + exists(NodeEx midNode, DataFlowType contentType | + midNode = mid.getNodeEx() and + ap1 = mid.getAp() and + storeEx(midNode, tc, node, contentType) and + ap2.getHead() = tc and + ap2.len() = unbindInt(ap1.len() + 1) and + compatibleTypes(ap1.getType(), contentType) + ) + } + + pragma[nomagic] + private predicate apConsFwd(PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2) { + partialPathStoreStep(_, ap1, tc, _, ap2) + } + + pragma[nomagic] + private predicate partialPathReadStep( + PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc + ) { + exists(NodeEx midNode | + midNode = mid.getNodeEx() and + ap = mid.getAp() and + read(midNode, tc.getContent(), node) and + ap.getHead() = tc and + cc = mid.getCallContext() + ) + } + + private predicate partialPathOutOfCallable0( + PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, + PartialAccessPath ap + ) { + pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and + state = mid.getState() and + innercc = mid.getCallContext() and + innercc instanceof CallContextNoCall and ap = mid.getAp() - ) - } + } - pragma[noinline] - private predicate partialPathThroughCallable0( - DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | - partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _, config) and - paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap, config) - ) - } + pragma[nomagic] + private predicate partialPathOutOfCallable1( + PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, + CallContext cc, PartialAccessPath ap + ) { + exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | + partialPathOutOfCallable0(mid, pos, state, innercc, ap) and + c = pos.getCallable() and + kind = pos.getKind() and + resolveReturn(innercc, c, call) + | + if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + ) + } - private predicate partialPathThroughCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ReturnKindExt kind | - partialPathThroughCallable0(call, mid, kind, state, cc, ap, config) and - out.asNode() = kind.getAnOutNode(call) - ) - } + private predicate partialPathOutOfCallable( + PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap + ) { + exists(ReturnKindExt kind, DataFlowCall call | + partialPathOutOfCallable1(mid, call, kind, state, cc, ap) + | + out.asNode() = kind.getAnOutNode(call) + ) + } - pragma[nomagic] - private predicate revPartialPathStep( - PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, - TRevSummaryCtx3 sc3, RevPartialAccessPath ap, Configuration config - ) { - localFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - jumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - revPartialPathReadStep(mid, _, _, node, ap) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(RevPartialAccessPath ap0, Content c | - revPartialPathStoreStep(mid, ap0, c, node, config) and + pragma[noinline] + private predicate partialPathIntoArg( + PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, + DataFlowCall call, PartialAccessPath ap + ) { + exists(ArgNode arg, ArgumentPosition apos | + arg = mid.getNodeEx().asNode() and + state = mid.getState() and + cc = mid.getCallContext() and + arg.argumentOf(call, apos) and + ap = mid.getAp() and + parameterMatch(ppos, apos) + ) + } + + pragma[nomagic] + private predicate partialPathIntoCallable0( + PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, + CallContext outercc, DataFlowCall call, PartialAccessPath ap + ) { + partialPathIntoArg(mid, pos, state, outercc, call, ap) and + callable = resolveCall(call, outercc) + } + + private predicate partialPathIntoCallable( + PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, + CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, + DataFlowCall call, PartialAccessPath ap + ) { + exists(ParameterPosition pos, DataFlowCallable callable | + partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap) and + p.isParameterOf(callable, pos) and + sc1 = TSummaryCtx1Param(p) and + sc2 = TSummaryCtx2Some(state) and + sc3 = TSummaryCtx3Some(ap) + | + if recordDataFlowCallSite(call, callable) + then innercc = TSpecificCall(call) + else innercc = TSomeCall() + ) + } + + pragma[nomagic] + private predicate paramFlowsThroughInPartialPath( + ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, + TSummaryCtx3 sc3, PartialAccessPath ap + ) { + exists(PartialPathNodeFwd mid, RetNodeEx ret | + mid.getNodeEx() = ret and + kind = ret.getKind() and + state = mid.getState() and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + ap = mid.getAp() + ) + } + + pragma[noinline] + private predicate partialPathThroughCallable0( + DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, + CallContext cc, PartialAccessPath ap + ) { + exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | + partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _) and + paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap) + ) + } + + private predicate partialPathThroughCallable( + PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap + ) { + exists(DataFlowCall call, ReturnKindExt kind | + partialPathThroughCallable0(call, mid, kind, state, cc, ap) and + out.asNode() = kind.getAnOutNode(call) + ) + } + + pragma[nomagic] + private predicate revPartialPathStep( + PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, + TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, RevPartialAccessPath ap + ) { + localFlowStepEx(node, mid.getNodeEx()) and state = mid.getState() and sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and sc3 = mid.getSummaryCtx3() and - apConsRev(ap, c, ap0, config) - ) - or - exists(ParamNodeEx p | - mid.getNodeEx() = p and - viableParamArgEx(_, p, node) and + ap = mid.getAp() + or + additionalLocalFlowStep(node, mid.getNodeEx()) and state = mid.getState() and sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and sc3 = mid.getSummaryCtx3() and + mid.getAp() instanceof RevPartialAccessPathNil and + ap = TRevPartialNil() + or + additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState()) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + mid.getAp() instanceof RevPartialAccessPathNil and + ap = TRevPartialNil() + or + jumpStepEx(node, mid.getNodeEx()) and + state = mid.getState() and sc1 = TRevSummaryCtx1None() and sc2 = TRevSummaryCtx2None() and sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - or - exists(ReturnPosition pos | - revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap, config) and - pos = getReturnPosition(node.asNode()) - ) - or - revPartialPathThroughCallable(mid, node, state, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - pragma[inline] - private predicate revPartialPathReadStep( - PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, - RevPartialAccessPath ap2 - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - read(node, c, midNode, mid.getConfiguration()) and - ap2.getHead() = c and - ap2.len() = unbindInt(ap1.len() + 1) - ) - } - - pragma[nomagic] - private predicate apConsRev( - RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeRev mid | - revPartialPathReadStep(mid, ap1, c, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathStoreStep( - PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config - ) { - exists(NodeEx midNode, TypedContent tc | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - store(node, tc, midNode, _, config) and - ap.getHead() = c and - config = mid.getConfiguration() and - tc.getContent() = c - ) - } - - pragma[nomagic] - private predicate revPartialPathIntoReturn( - PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, - TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap, - Configuration config - ) { - exists(NodeEx out | - mid.getNodeEx() = out and - mid.getState() = state and - viableReturnPosOutEx(call, pos, out) and - sc1 = TRevSummaryCtx1Some(pos) and - sc2 = TRevSummaryCtx2Some(state) and - sc3 = TRevSummaryCtx3Some(ap) and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathFlowsThrough( - ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, - TRevSummaryCtx3Some sc3, RevPartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | - mid.getNodeEx() = p and - mid.getState() = state and - p.getPosition() = ppos and + ap = mid.getAp() + or + additionalJumpStep(node, mid.getNodeEx()) and + state = mid.getState() and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + sc3 = TRevSummaryCtx3None() and + mid.getAp() instanceof RevPartialAccessPathNil and + ap = TRevPartialNil() + or + additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState()) and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + sc3 = TRevSummaryCtx3None() and + mid.getAp() instanceof RevPartialAccessPathNil and + ap = TRevPartialNil() + or + revPartialPathReadStep(mid, _, _, node, ap) and + state = mid.getState() and sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } + sc3 = mid.getSummaryCtx3() + or + exists(RevPartialAccessPath ap0, Content c | + revPartialPathStoreStep(mid, ap0, c, node) and + state = mid.getState() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + apConsRev(ap, c, ap0) + ) + or + exists(ParamNodeEx p | + mid.getNodeEx() = p and + viableParamArgEx(_, p, node) and + state = mid.getState() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + sc3 = TRevSummaryCtx3None() and + ap = mid.getAp() + ) + or + exists(ReturnPosition pos | + revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap) and + pos = getReturnPosition(node.asNode()) + ) + or + revPartialPathThroughCallable(mid, node, state, ap) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() + } - pragma[nomagic] - private predicate revPartialPathThroughCallable0( - DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, - RevPartialAccessPath ap, Configuration config - ) { - exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | - revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _, config) and - revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap, config) - ) - } + pragma[inline] + private predicate revPartialPathReadStep( + PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, + RevPartialAccessPath ap2 + ) { + exists(NodeEx midNode | + midNode = mid.getNodeEx() and + ap1 = mid.getAp() and + read(node, c, midNode) and + ap2.getHead() = c and + ap2.len() = unbindInt(ap1.len() + 1) + ) + } - pragma[nomagic] - private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ArgumentPosition pos | - revPartialPathThroughCallable0(call, mid, pos, state, ap, config) and - node.asNode().(ArgNode).argumentOf(call, pos) - ) + pragma[nomagic] + private predicate apConsRev(RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2) { + revPartialPathReadStep(_, ap1, c, _, ap2) + } + + pragma[nomagic] + private predicate revPartialPathStoreStep( + PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node + ) { + exists(NodeEx midNode, TypedContent tc | + midNode = mid.getNodeEx() and + ap = mid.getAp() and + storeEx(node, tc, midNode, _) and + ap.getHead() = c and + tc.getContent() = c + ) + } + + pragma[nomagic] + private predicate revPartialPathIntoReturn( + PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, + TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap + ) { + exists(NodeEx out | + mid.getNodeEx() = out and + mid.getState() = state and + viableReturnPosOutEx(call, pos, out) and + sc1 = TRevSummaryCtx1Some(pos) and + sc2 = TRevSummaryCtx2Some(state) and + sc3 = TRevSummaryCtx3Some(ap) and + ap = mid.getAp() + ) + } + + pragma[nomagic] + private predicate revPartialPathFlowsThrough( + ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, + TRevSummaryCtx3Some sc3, RevPartialAccessPath ap + ) { + exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | + mid.getNodeEx() = p and + mid.getState() = state and + p.getPosition() = ppos and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + ap = mid.getAp() and + parameterMatch(ppos, apos) + ) + } + + pragma[nomagic] + private predicate revPartialPathThroughCallable0( + DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, + RevPartialAccessPath ap + ) { + exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | + revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _) and + revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap) + ) + } + + pragma[nomagic] + private predicate revPartialPathThroughCallable( + PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap + ) { + exists(DataFlowCall call, ArgumentPosition pos | + revPartialPathThroughCallable0(call, mid, pos, state, ap) and + node.asNode().(ArgNode).argumentOf(call, pos) + ) + } + + private predicate partialFlow(PartialPathNode source, PartialPathNode node) { + source.isFwdSource() and + node = source.getASuccessor+() + } + + private predicate revPartialFlow(PartialPathNode node, PartialPathNode sink) { + sink.isRevSink() and + node.getASuccessor+() = sink + } + + /** + * Holds if there is a partial data flow path from `source` to `node`. The + * approximate distance between `node` and the closest source is `dist` and + * is restricted to be less than or equal to `explorationLimit()`. This + * predicate completely disregards sink definitions. + * + * This predicate is intended for data-flow exploration and debugging and may + * perform poorly if the number of sources is too big and/or the exploration + * limit is set too high without using barriers. + * + * To use this in a `path-problem` query, import the module `PartialPathGraph`. + */ + predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { + partialFlow(source, node) and + dist = node.getSourceDistance() + } + + /** + * Holds if there is a partial data flow path from `node` to `sink`. The + * approximate distance between `node` and the closest sink is `dist` and + * is restricted to be less than or equal to `explorationLimit()`. This + * predicate completely disregards source definitions. + * + * This predicate is intended for data-flow exploration and debugging and may + * perform poorly if the number of sinks is too big and/or the exploration + * limit is set too high without using barriers. + * + * To use this in a `path-problem` query, import the module `PartialPathGraph`. + * + * Note that reverse flow has slightly lower precision than the corresponding + * forward flow, as reverse flow disregards type pruning among other features. + */ + predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { + revPartialFlow(node, sink) and + dist = node.getSinkDistance() + } } } - -import FlowExploration - -private predicate partialFlow( - PartialPathNode source, PartialPathNode node, Configuration configuration -) { - source.getConfiguration() = configuration and - source.isFwdSource() and - node = source.getASuccessor+() -} - -private predicate revPartialFlow( - PartialPathNode node, PartialPathNode sink, Configuration configuration -) { - sink.getConfiguration() = configuration and - sink.isRevSink() and - node.getASuccessor+() = sink -} diff --git a/go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl1.qll b/go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl1.qll new file mode 100644 index 00000000000..e6bdc74cceb --- /dev/null +++ b/go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl1.qll @@ -0,0 +1,396 @@ +/** + * DEPRECATED: Use `Make` and `MakeWithState` instead. + * + * Provides a `Configuration` class backwards-compatible interface to the data + * flow library. + */ + +private import DataFlowImplCommon +private import DataFlowImplSpecific::Private +import DataFlowImplSpecific::Public +private import DataFlowImpl +import DataFlowImplCommonPublic +import FlowStateString + +/** + * A configuration of interprocedural data flow analysis. This defines + * sources, sinks, and any other configurable aspect of the analysis. Each + * use of the global data flow library must define its own unique extension + * of this abstract class. To create a configuration, extend this class with + * a subclass whose characteristic predicate is a unique singleton string. + * For example, write + * + * ```ql + * class MyAnalysisConfiguration extends DataFlow::Configuration { + * MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" } + * // Override `isSource` and `isSink`. + * // Optionally override `isBarrier`. + * // Optionally override `isAdditionalFlowStep`. + * } + * ``` + * Conceptually, this defines a graph where the nodes are `DataFlow::Node`s and + * the edges are those data-flow steps that preserve the value of the node + * along with any additional edges defined by `isAdditionalFlowStep`. + * Specifying nodes in `isBarrier` will remove those nodes from the graph, and + * specifying nodes in `isBarrierIn` and/or `isBarrierOut` will remove in-going + * and/or out-going edges from those nodes, respectively. + * + * Then, to query whether there is flow between some `source` and `sink`, + * write + * + * ```ql + * exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink)) + * ``` + * + * Multiple configurations can coexist, but two classes extending + * `DataFlow::Configuration` should never depend on each other. One of them + * should instead depend on a `DataFlow2::Configuration`, a + * `DataFlow3::Configuration`, or a `DataFlow4::Configuration`. + */ +abstract class Configuration extends string { + bindingset[this] + Configuration() { any() } + + /** + * Holds if `source` is a relevant data flow source. + */ + predicate isSource(Node source) { none() } + + /** + * Holds if `source` is a relevant data flow source with the given initial + * `state`. + */ + predicate isSource(Node source, FlowState state) { none() } + + /** + * Holds if `sink` is a relevant data flow sink. + */ + predicate isSink(Node sink) { none() } + + /** + * Holds if `sink` is a relevant data flow sink accepting `state`. + */ + predicate isSink(Node sink, FlowState state) { none() } + + /** + * Holds if data flow through `node` is prohibited. This completely removes + * `node` from the data flow graph. + */ + predicate isBarrier(Node node) { none() } + + /** + * Holds if data flow through `node` is prohibited when the flow state is + * `state`. + */ + predicate isBarrier(Node node, FlowState state) { none() } + + /** Holds if data flow into `node` is prohibited. */ + predicate isBarrierIn(Node node) { none() } + + /** Holds if data flow out of `node` is prohibited. */ + predicate isBarrierOut(Node node) { none() } + + /** + * DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead. + * + * Holds if data flow through nodes guarded by `guard` is prohibited. + */ + deprecated predicate isBarrierGuard(BarrierGuard guard) { none() } + + /** + * DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead. + * + * Holds if data flow through nodes guarded by `guard` is prohibited when + * the flow state is `state` + */ + deprecated predicate isBarrierGuard(BarrierGuard guard, FlowState state) { none() } + + /** + * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. + */ + predicate isAdditionalFlowStep(Node node1, Node node2) { none() } + + /** + * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. + * This step is only applicable in `state1` and updates the flow state to `state2`. + */ + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + none() + } + + /** + * Holds if an arbitrary number of implicit read steps of content `c` may be + * taken at `node`. + */ + predicate allowImplicitRead(Node node, ContentSet c) { none() } + + /** + * Gets the virtual dispatch branching limit when calculating field flow. + * This can be overridden to a smaller value to improve performance (a + * value of 0 disables field flow), or a larger value to get more results. + */ + int fieldFlowBranchLimit() { result = 2 } + + /** + * Gets a data flow configuration feature to add restrictions to the set of + * valid flow paths. + * + * - `FeatureHasSourceCallContext`: + * Assume that sources have some existing call context to disallow + * conflicting return-flow directly following the source. + * - `FeatureHasSinkCallContext`: + * Assume that sinks have some existing call context to disallow + * conflicting argument-to-parameter flow directly preceding the sink. + * - `FeatureEqualSourceSinkCallContext`: + * Implies both of the above and additionally ensures that the entire flow + * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. + */ + FlowFeature getAFeature() { none() } + + /** Holds if sources should be grouped in the result of `hasFlowPath`. */ + predicate sourceGrouping(Node source, string sourceGroup) { none() } + + /** Holds if sinks should be grouped in the result of `hasFlowPath`. */ + predicate sinkGrouping(Node sink, string sinkGroup) { none() } + + /** + * Holds if data may flow from `source` to `sink` for this configuration. + */ + predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) } + + /** + * Holds if data may flow from `source` to `sink` for this configuration. + * + * The corresponding paths are generated from the end-points and the graph + * included in the module `PathGraph`. + */ + predicate hasFlowPath(PathNode source, PathNode sink) { hasFlowPath(source, sink, this) } + + /** + * Holds if data may flow from some source to `sink` for this configuration. + */ + predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) } + + /** + * Holds if data may flow from some source to `sink` for this configuration. + */ + predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } + + /** + * DEPRECATED: Use `FlowExploration` instead. + * + * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` + * measured in approximate number of interprocedural steps. + */ + deprecated int explorationLimit() { none() } + + /** + * Holds if hidden nodes should be included in the data flow graph. + * + * This feature should only be used for debugging or when the data flow graph + * is not visualized (for example in a `path-problem` query). + */ + predicate includeHiddenNodes() { none() } +} + +/** + * This class exists to prevent mutual recursion between the user-overridden + * member predicates of `Configuration` and the rest of the data-flow library. + * Good performance cannot be guaranteed in the presence of such recursion, so + * it should be replaced by using more than one copy of the data flow library. + */ +abstract private class ConfigurationRecursionPrevention extends Configuration { + bindingset[this] + ConfigurationRecursionPrevention() { any() } + + override predicate hasFlow(Node source, Node sink) { + strictcount(Node n | this.isSource(n)) < 0 + or + strictcount(Node n | this.isSource(n, _)) < 0 + or + strictcount(Node n | this.isSink(n)) < 0 + or + strictcount(Node n | this.isSink(n, _)) < 0 + or + strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0 + or + strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, _, n2, _)) < 0 + or + super.hasFlow(source, sink) + } +} + +/** A bridge class to access the deprecated `isBarrierGuard`. */ +private class BarrierGuardGuardedNodeBridge extends Unit { + abstract predicate guardedNode(Node n, Configuration config); + + abstract predicate guardedNode(Node n, FlowState state, Configuration config); +} + +private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { + deprecated override predicate guardedNode(Node n, Configuration config) { + exists(BarrierGuard g | + config.isBarrierGuard(g) and + n = g.getAGuardedNode() + ) + } + + deprecated override predicate guardedNode(Node n, FlowState state, Configuration config) { + exists(BarrierGuard g | + config.isBarrierGuard(g, state) and + n = g.getAGuardedNode() + ) + } +} + +private FlowState relevantState(Configuration config) { + config.isSource(_, result) or + config.isSink(_, result) or + config.isBarrier(_, result) or + config.isAdditionalFlowStep(_, result, _, _) or + config.isAdditionalFlowStep(_, _, _, result) +} + +private newtype TConfigState = + TMkConfigState(Configuration config, FlowState state) { + state = relevantState(config) or state instanceof FlowStateEmpty + } + +private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) } + +private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) } + +private predicate singleConfiguration() { 1 = strictcount(Configuration c) } + +private module Config implements FullStateConfigSig { + class FlowState = TConfigState; + + predicate isSource(Node source, FlowState state) { + getConfig(state).isSource(source, getState(state)) + or + getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty + } + + predicate isSink(Node sink, FlowState state) { + getConfig(state).isSink(sink, getState(state)) + or + getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty + } + + predicate isBarrier(Node node) { none() } + + predicate isBarrier(Node node, FlowState state) { + getConfig(state).isBarrier(node, getState(state)) or + getConfig(state).isBarrier(node) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state)) + } + + predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) } + + predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) } + + predicate isAdditionalFlowStep(Node node1, Node node2) { + singleConfiguration() and + any(Configuration config).isAdditionalFlowStep(node1, node2) + } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and + getConfig(state2) = getConfig(state1) + or + not singleConfiguration() and + getConfig(state1).isAdditionalFlowStep(node1, node2) and + state2 = state1 + } + + predicate allowImplicitRead(Node node, ContentSet c) { + any(Configuration config).allowImplicitRead(node, c) + } + + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } + + FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } + + predicate sourceGrouping(Node source, string sourceGroup) { + any(Configuration config).sourceGrouping(source, sourceGroup) + } + + predicate sinkGrouping(Node sink, string sinkGroup) { + any(Configuration config).sinkGrouping(sink, sinkGroup) + } + + predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() } +} + +private import Impl as I +import I + +/** + * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. + * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. + */ +class PathNode instanceof I::PathNode { + /** Gets a textual representation of this element. */ + final string toString() { result = super.toString() } + + /** + * Gets a textual representation of this element, including a textual + * representation of the call context. + */ + final string toStringWithContext() { result = super.toStringWithContext() } + + /** + * 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/). + */ + final predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + + /** Gets the underlying `Node`. */ + final Node getNode() { result = super.getNode() } + + /** Gets the `FlowState` of this node. */ + final FlowState getState() { result = getState(super.getState()) } + + /** Gets the associated configuration. */ + final Configuration getConfiguration() { result = getConfig(super.getState()) } + + /** Gets a successor of this node, if any. */ + final PathNode getASuccessor() { result = super.getASuccessor() } + + /** Holds if this node is a source. */ + final predicate isSource() { super.isSource() } + + /** Holds if this node is a grouping of source nodes. */ + final predicate isSourceGroup(string group) { super.isSourceGroup(group) } + + /** Holds if this node is a grouping of sink nodes. */ + final predicate isSinkGroup(string group) { super.isSinkGroup(group) } +} + +private predicate hasFlow(Node source, Node sink, Configuration config) { + exists(PathNode source0, PathNode sink0 | + hasFlowPath(source0, sink0, config) and + source0.getNode() = source and + sink0.getNode() = sink + ) +} + +private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) { + hasFlowPath(source, sink) and source.getConfiguration() = config +} + +private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) } + +predicate flowsTo = hasFlow/3; diff --git a/go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl2.qll b/go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl2.qll index 1b969756b09..e6bdc74cceb 100644 --- a/go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl2.qll +++ b/go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl2.qll @@ -1,16 +1,16 @@ /** - * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from - * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed - * through the `Configuration` class. This file exists in several identical - * copies, allowing queries to use multiple `Configuration` classes that depend - * on each other without introducing mutual recursion among those configurations. + * DEPRECATED: Use `Make` and `MakeWithState` instead. + * + * Provides a `Configuration` class backwards-compatible interface to the data + * flow library. */ private import DataFlowImplCommon private import DataFlowImplSpecific::Private import DataFlowImplSpecific::Public +private import DataFlowImpl import DataFlowImplCommonPublic +import FlowStateString /** * A configuration of interprocedural data flow analysis. This defines @@ -144,6 +144,10 @@ abstract class Configuration extends string { * - `FeatureEqualSourceSinkCallContext`: * Implies both of the above and additionally ensures that the entire flow * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. */ FlowFeature getAFeature() { none() } @@ -156,7 +160,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from `source` to `sink` for this configuration. */ - predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } + predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) } /** * Holds if data may flow from `source` to `sink` for this configuration. @@ -169,9 +173,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from some source to `sink` for this configuration. */ - predicate hasFlowTo(Node sink) { - sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode() - } + predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) } /** * Holds if data may flow from some source to `sink` for this configuration. @@ -179,10 +181,12 @@ abstract class Configuration extends string { predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } /** + * DEPRECATED: Use `FlowExploration` instead. + * * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` * measured in approximate number of interprocedural steps. */ - int explorationLimit() { none() } + deprecated int explorationLimit() { none() } /** * Holds if hidden nodes should be included in the data flow graph. @@ -191,49 +195,6 @@ abstract class Configuration extends string { * is not visualized (for example in a `path-problem` query). */ predicate includeHiddenNodes() { none() } - - /** - * Holds if there is a partial data flow path from `source` to `node`. The - * approximate distance between `node` and the closest source is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards sink definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sources is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - */ - final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { - partialFlow(source, node, this) and - dist = node.getSourceDistance() - } - - /** - * Holds if there is a partial data flow path from `node` to `sink`. The - * approximate distance between `node` and the closest sink is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards source definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sinks is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - * - * Note that reverse flow has slightly lower precision than the corresponding - * forward flow, as reverse flow disregards type pruning among other features. - */ - final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { - revPartialFlow(node, sink, this) and - dist = node.getSinkDistance() - } } /** @@ -263,90 +224,6 @@ abstract private class ConfigurationRecursionPrevention extends Configuration { } } -private newtype TNodeEx = - TNodeNormal(Node n) or - TNodeImplicitRead(Node n, boolean hasRead) { - any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true] - } - -private class NodeEx extends TNodeEx { - string toString() { - result = this.asNode().toString() - or - exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") - } - - Node asNode() { this = TNodeNormal(result) } - - predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } - - Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } - - pragma[nomagic] - private DataFlowCallable getEnclosingCallable0() { - nodeEnclosingCallable(this.projectToNode(), result) - } - - pragma[inline] - DataFlowCallable getEnclosingCallable() { - pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) - } - - pragma[nomagic] - private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } - - pragma[inline] - DataFlowType getDataFlowType() { - pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) - } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -private class ArgNodeEx extends NodeEx { - ArgNodeEx() { this.asNode() instanceof ArgNode } -} - -private class ParamNodeEx extends NodeEx { - ParamNodeEx() { this.asNode() instanceof ParamNode } - - predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { - this.asNode().(ParamNode).isParameterOf(c, pos) - } - - ParameterPosition getPosition() { this.isParameterOf(_, result) } -} - -private class RetNodeEx extends NodeEx { - RetNodeEx() { this.asNode() instanceof ReturnNodeExt } - - ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } - - ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } -} - -private predicate inBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierIn(n) - | - config.isSource(n) or config.isSource(n, _) - ) -} - -private predicate outBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierOut(n) - | - config.isSink(n) or config.isSink(n, _) - ) -} - /** A bridge class to access the deprecated `isBarrierGuard`. */ private class BarrierGuardGuardedNodeBridge extends Unit { abstract predicate guardedNode(Node n, Configuration config); @@ -370,3019 +247,94 @@ private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { } } -pragma[nomagic] -private predicate fullBarrier(NodeEx node, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n) +private FlowState relevantState(Configuration config) { + config.isSource(_, result) or + config.isSink(_, result) or + config.isBarrier(_, result) or + config.isAdditionalFlowStep(_, result, _, _) or + config.isAdditionalFlowStep(_, _, _, result) +} + +private newtype TConfigState = + TMkConfigState(Configuration config, FlowState state) { + state = relevantState(config) or state instanceof FlowStateEmpty + } + +private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) } + +private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) } + +private predicate singleConfiguration() { 1 = strictcount(Configuration c) } + +private module Config implements FullStateConfigSig { + class FlowState = TConfigState; + + predicate isSource(Node source, FlowState state) { + getConfig(state).isSource(source, getState(state)) or - config.isBarrierIn(n) and - not config.isSource(n) and - not config.isSource(n, _) + getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty + } + + predicate isSink(Node sink, FlowState state) { + getConfig(state).isSink(sink, getState(state)) or - config.isBarrierOut(n) and - not config.isSink(n) and - not config.isSink(n, _) + getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty + } + + predicate isBarrier(Node node) { none() } + + predicate isBarrier(Node node, FlowState state) { + getConfig(state).isBarrier(node, getState(state)) or + getConfig(state).isBarrier(node) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state)) + } + + predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) } + + predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) } + + predicate isAdditionalFlowStep(Node node1, Node node2) { + singleConfiguration() and + any(Configuration config).isAdditionalFlowStep(node1, node2) + } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and + getConfig(state2) = getConfig(state1) or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config) - ) -} - -pragma[nomagic] -private predicate stateBarrier(NodeEx node, FlowState state, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n, state) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config) - ) -} - -pragma[nomagic] -private predicate sourceNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSource(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSource(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -pragma[nomagic] -private predicate sinkNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSink(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSink(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -/** Provides the relevant barriers for a step from `node1` to `node2`. */ -pragma[inline] -private predicate stepFilter(NodeEx node1, NodeEx node2, Configuration config) { - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) -} - -/** - * Holds if data can flow in one local step from `node1` to `node2`. - */ -private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.asNode() = n and - node2.isImplicitReadNode(n, false) and - not fullBarrier(node1, config) - ) -} - -/** - * Holds if the additional step from `node1` to `node2` does not jump between callables. - */ -private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.isImplicitReadNode(n, true) and - node2.asNode() = n and - not fullBarrier(node2, config) - ) -} - -private predicate additionalLocalStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) - ) -} - -/** - * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. - */ -private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if the additional step from `node1` to `node2` jumps between callables. - */ -private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate additionalJumpStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -pragma[nomagic] -private predicate readSet(NodeEx node1, ContentSet c, NodeEx node2, Configuration config) { - readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and - stepFilter(node1, node2, config) - or - exists(Node n | - node2.isImplicitReadNode(n, true) and - node1.isImplicitReadNode(n, _) and - config.allowImplicitRead(n, c) - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(ContentSet cs | - readSet(node1, cs, node2, config) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate clearsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - clearsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate expectsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - expectsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -pragma[nomagic] -private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } - -pragma[nomagic] -private predicate hasReadStep(Content c, Configuration config) { read(_, c, _, config) } - -pragma[nomagic] -private predicate store( - NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config -) { - store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), - contentType) and - hasReadStep(tc.getContent(), config) and - stepFilter(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { - viableReturnPosOut(call, pos, out.asNode()) -} - -pragma[nomagic] -private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { - viableParamArg(call, p.asNode(), arg.asNode()) -} - -/** - * Holds if field flow should be used for the given configuration. - */ -private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } - -private predicate hasSourceCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSourceCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate hasSinkCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSinkCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if flow from `p` to a return node of kind `kind` is allowed. - * - * We don't expect a parameter to return stored in itself, unless - * explicitly allowed - */ -bindingset[p, kind] -private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { - exists(ParameterPosition pos | p.isParameterOf(_, pos) | - not kind.(ParamUpdateReturnKind).getPosition() = pos - or - allowParameterReturnInSelfCached(p.asNode()) - ) -} - -private module Stage1 implements StageSig { - class Ap extends int { - // workaround for bad functionality-induced joins (happens when using `Unit`) - pragma[nomagic] - Ap() { this in [0 .. 1] and this < 1 } - } - - private class Cc = boolean; - - /* Begin: Stage 1 logic. */ - /** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `cc` records whether the node is reached through an - * argument in a call. - */ - private predicate fwdFlow(NodeEx node, Cc cc, Configuration config) { - sourceNode(node, _, config) and - if hasSourceCallCtx(config) then cc = true else cc = false - or - exists(NodeEx mid | fwdFlow(mid, cc, config) | - localFlowStep(mid, node, config) or - additionalLocalFlowStep(mid, node, config) or - additionalLocalStateStep(mid, _, node, _, config) - ) - or - exists(NodeEx mid | fwdFlow(mid, _, config) and cc = false | - jumpStep(mid, node, config) or - additionalJumpStep(mid, node, config) or - additionalJumpStateStep(mid, _, node, _, config) - ) - or - // store - exists(NodeEx mid | - useFieldFlow(config) and - fwdFlow(mid, cc, config) and - store(mid, _, node, _, config) - ) - or - // read - exists(ContentSet c | - fwdFlowReadSet(c, node, cc, config) and - fwdFlowConsCandSet(c, _, config) - ) - or - // flow into a callable - fwdFlowIn(_, _, _, node, config) and - cc = true - or - // flow out of a callable - fwdFlowOut(_, node, false, config) and - cc = false - or - // flow through a callable - exists(DataFlowCall call | - fwdFlowOutFromArg(call, node, config) and - fwdFlowIsEntered(call, cc, config) - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowIn( - DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p, Configuration config - ) { - // call context cannot help reduce virtual dispatch - fwdFlow(arg, cc, config) and - viableParamArgEx(call, p, arg) and - not fullBarrier(p, config) and - ( - cc = false - or - cc = true and - not reducedViableImplInCallContext(call, _, _) - ) - or - // call context may help reduce virtual dispatch - exists(DataFlowCallable target | - fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target, config) and - target = viableImplInSomeFwdFlowCallContextExt(call, config) and - cc = true - ) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - fwdFlowIn(call, _, cc, _, config) - } - - pragma[nomagic] - private predicate fwdFlowInReducedViableImplInSomeCallContext( - DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target, Configuration config - ) { - fwdFlow(arg, true, config) and - viableParamArgEx(call, p, arg) and - reducedViableImplInCallContext(call, _, _) and - target = p.getEnclosingCallable() and - not fullBarrier(p, config) - } - - /** - * Gets a viable dispatch target of `call` in the context `ctx`. This is - * restricted to those `call`s for which a context might make a difference, - * and to `ctx`s that are reachable in `fwdFlow`. - */ - pragma[nomagic] - private DataFlowCallable viableImplInSomeFwdFlowCallContextExt( - DataFlowCall call, Configuration config - ) { - exists(DataFlowCall ctx | - fwdFlowIsEntered(ctx, _, config) and - result = viableImplInCallContextExt(call, ctx) - ) - } - - private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) } - - pragma[nomagic] - private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc, Configuration config) { - exists(NodeEx mid | - fwdFlow(mid, cc, config) and - readSet(mid, c, node, config) - ) - } - - /** - * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - fwdFlow(mid, _, config) and - store(mid, tc, node, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `cs` may be interpreted in a read as the target of some store - * into `c`, in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCandSet(ContentSet cs, Content c, Configuration config) { - fwdFlowConsCand(c, config) and - c = cs.getAReadContent() - } - - pragma[nomagic] - private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { - exists(RetNodeEx ret | - fwdFlow(ret, cc, config) and - ret.getReturnPosition() = pos - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) { - exists(ReturnPosition pos | - fwdFlowReturnPosition(pos, cc, config) and - viableReturnPosOutEx(call, pos, out) and - not fullBarrier(out, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) { - fwdFlowOut(call, out, true, config) - } - - private predicate stateStepFwd(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1 | - additionalLocalStateStep(node1, state1, _, state2, config) or - additionalJumpStateStep(node1, state1, _, state2, config) - | - fwdFlow(node1, config) - ) - } - - private predicate fwdFlowState(FlowState state, Configuration config) { - sourceNode(_, state, config) - or - exists(FlowState state0 | - fwdFlowState(state0, config) and - stateStepFwd(state0, state, config) - ) - } - - /** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink. - */ - pragma[nomagic] - private predicate revFlow(NodeEx node, boolean toReturn, Configuration config) { - revFlow0(node, toReturn, config) and - fwdFlow(node, config) - } - - pragma[nomagic] - private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) { - exists(FlowState state | - fwdFlow(node, pragma[only_bind_into](config)) and - sinkNode(node, state, config) and - fwdFlowState(state, pragma[only_bind_into](config)) and - if hasSinkCallCtx(config) then toReturn = true else toReturn = false - ) - or - exists(NodeEx mid | revFlow(mid, toReturn, config) | - localFlowStep(node, mid, config) or - additionalLocalFlowStep(node, mid, config) or - additionalLocalStateStep(node, _, mid, _, config) - ) - or - exists(NodeEx mid | revFlow(mid, _, config) and toReturn = false | - jumpStep(node, mid, config) or - additionalJumpStep(node, mid, config) or - additionalJumpStateStep(node, _, mid, _, config) - ) - or - // store - exists(Content c | - revFlowStore(c, node, toReturn, config) and - revFlowConsCand(c, config) - ) - or - // read - exists(NodeEx mid, ContentSet c | - readSet(node, c, mid, config) and - fwdFlowConsCandSet(c, _, pragma[only_bind_into](config)) and - revFlow(mid, toReturn, pragma[only_bind_into](config)) - ) - or - // flow into a callable - revFlowIn(_, node, false, config) and - toReturn = false - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(pos, config) and - node.(RetNodeEx).getReturnPosition() = pos and - toReturn = true - ) - or - // flow through a callable - exists(DataFlowCall call | - revFlowInToReturn(call, node, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - /** - * Holds if `c` is the target of a read in the flow covered by `revFlow`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, ContentSet cs | - fwdFlow(node, pragma[only_bind_into](config)) and - readSet(node, cs, mid, config) and - fwdFlowConsCandSet(cs, c, pragma[only_bind_into](config)) and - revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) { - exists(NodeEx mid, TypedContent tc | - revFlow(mid, toReturn, pragma[only_bind_into](config)) and - fwdFlowConsCand(c, pragma[only_bind_into](config)) and - store(node, tc, mid, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `c` is the target of both a read and a store in the flow covered - * by `revFlow`. - */ - pragma[nomagic] - additional predicate revFlowIsReadAndStored(Content c, Configuration conf) { - revFlowConsCand(c, conf) and - revFlowStore(c, _, _, conf) - } - - pragma[nomagic] - additional predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config - ) { - fwdFlowReturnPosition(pos, _, config) and - viableReturnPosOutEx(call, pos, out) - } - - pragma[nomagic] - private predicate revFlowOut(ReturnPosition pos, Configuration config) { - exists(NodeEx out | - revFlow(out, _, config) and - viableReturnPosOutNodeCandFwd1(_, pos, out, config) - ) - } - - pragma[nomagic] - additional predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config - ) { - fwdFlowIn(call, arg, _, p, config) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate revFlowIn( - DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config - ) { - exists(ParamNodeEx p | - revFlow(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) { - revFlowIn(call, arg, true, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(NodeEx out | - revFlow(out, toReturn, config) and - fwdFlowOutFromArg(call, out, config) - ) - } - - private predicate stateStepRev(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - additionalLocalStateStep(node1, state1, node2, state2, config) or - additionalJumpStateStep(node1, state1, node2, state2, config) - | - revFlow(node1, _, pragma[only_bind_into](config)) and - revFlow(node2, _, pragma[only_bind_into](config)) and - fwdFlowState(state1, pragma[only_bind_into](config)) and - fwdFlowState(state2, pragma[only_bind_into](config)) - ) - } - - additional predicate revFlowState(FlowState state, Configuration config) { - exists(NodeEx node | - sinkNode(node, state, config) and - revFlow(node, _, pragma[only_bind_into](config)) and - fwdFlowState(state, pragma[only_bind_into](config)) - ) - or - exists(FlowState state0 | - revFlowState(state0, config) and - stateStepRev(state, state0, config) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Content c | - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - revFlow(node2, pragma[only_bind_into](config)) and - store(node1, tc, node2, contentType, config) and - c = tc.getContent() and - exists(ap1) - ) - } - - pragma[nomagic] - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) { - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - read(n1, c, n2, pragma[only_bind_into](config)) and - revFlow(n2, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, config) and - exists(ap) - } - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, _, pragma[only_bind_into](config)) and - exists(state) and - exists(ap) - } - - private predicate throughFlowNodeCand(NodeEx node, Configuration config) { - revFlow(node, true, config) and - fwdFlow(node, true, config) and - not inBarrier(node, config) and - not outBarrier(node, config) - } - - /** Holds if flow may return from `callable`. */ - pragma[nomagic] - private predicate returnFlowCallableNodeCand( - DataFlowCallable callable, ReturnKindExt kind, Configuration config - ) { - exists(RetNodeEx ret | - throughFlowNodeCand(ret, config) and - callable = ret.getEnclosingCallable() and - kind = ret.getKind() - ) - } - - /** - * Holds if flow may enter through `p` and reach a return node making `p` a - * candidate for the origin of a summary. - */ - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(DataFlowCallable c, ReturnKindExt kind | - throughFlowNodeCand(p, config) and - returnFlowCallableNodeCand(c, kind, config) and - p.getEnclosingCallable() = c and - exists(ap) and - parameterFlowThroughAllowed(p, kind) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - throughFlowNodeCand(ret, config) and - kind = ret.getKind() and - exists(argAp) and - exists(ap) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, boolean toReturn | - revFlow(arg, toReturn, config) and - revFlowInToReturn(call, arg, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, config)) and - fields = count(Content f0 | fwdFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | fwdFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, config)) and - fields = count(Content f0 | revFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | revFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | revFlow(n, b, config)) - } - /* End: Stage 1 logic. */ -} - -pragma[noinline] -private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - localFlowStep(node1, node2, config) -} - -pragma[noinline] -private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - additionalLocalFlowStep(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCand1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config -) { - Stage1::revFlow(out, config) and - Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, Configuration config -) { - exists(ReturnPosition pos | - viableReturnPosOutNodeCand1(call, pos, out, config) and - pos = ret.getReturnPosition() and - kind = pos.getKind() and - Stage1::revFlow(ret, config) and - not outBarrier(ret, config) and - not inBarrier(out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCand1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config -) { - Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and - Stage1::revFlow(arg, config) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config -) { - viableParamArgNodeCand1(call, p, arg, config) and - Stage1::revFlow(p, config) and - not outBarrier(arg, config) and - not inBarrier(p, config) -} - -/** - * Gets the amount of forward branching on the origin of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int branch(NodeEx n1, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n1, _, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) - ) -} - -/** - * Gets the amount of backward branching on the target of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int join(NodeEx n2, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n, _, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) - ) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. The - * `allowsFieldFlow` flag indicates whether the branching is within the limit - * specified by the configuration. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, ret, kind, out, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(ret, pragma[only_bind_into](config)) and - j = join(out, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. The `allowsFieldFlow` flag indicates whether - * the branching is within the limit specified by the configuration. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config -) { - flowIntoCallNodeCand1(call, arg, p, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(arg, pragma[only_bind_into](config)) and - j = join(p, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -private signature module StageSig { - class Ap; - - predicate revFlow(NodeEx node, Configuration config); - - predicate revFlowAp(NodeEx node, Ap ap, Configuration config); - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config); - - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config); - - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config); - - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ); - - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ); - - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config); -} - -private module MkStage { - class ApApprox = PrevStage::Ap; - - signature module StageParam { - class Ap; - - class ApNil extends Ap; - - bindingset[result, ap] - ApApprox getApprox(Ap ap); - - ApNil getApNil(NodeEx node); - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail); - - /** - * An approximation of `Content` that corresponds to the precision level of - * `Ap`, such that the mappings from both `Ap` and `Content` to this type - * are functional. - */ - class ApHeadContent; - - ApHeadContent getHeadContent(Ap ap); - - ApHeadContent projectToHeadContent(Content c); - - class ApOption; - - ApOption apNone(); - - ApOption apSome(Ap ap); - - class Cc; - - class CcCall extends Cc; - - // TODO: member predicate on CcCall - predicate matchesCall(CcCall cc, DataFlowCall call); - - class CcNoCall extends Cc; - - Cc ccNone(); - - CcCall ccSomeCall(); - - class LocalCc; - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc); - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ); - - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config - ); - - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config - ); - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config); - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType); - } - - module Stage implements StageSig { - import Param - - /* Begin: Stage logic. */ - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - private predicate revFlowApAlias(NodeEx node, ApApprox apa, Configuration config) { - PrevStage::revFlowAp(node, apa, config) - } - - pragma[nomagic] - private predicate flowIntoCallApa( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa, - Configuration config - ) { - flowIntoCall(call, arg, p, allowsFieldFlow, config) and - PrevStage::revFlowAp(p, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(arg, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowOutOfCallApa( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - ApApprox apa, Configuration config - ) { - flowOutOfCall(call, ret, kind, out, allowsFieldFlow, config) and - PrevStage::revFlowAp(out, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(ret, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowThroughOutOfCall( - DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, - ApApprox argApa, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa, pragma[only_bind_into](config)) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) and - matchesCall(ccc, call) - ) - } - - /** - * Holds if `node` is reachable with access path `ap` from a source in the - * configuration `config`. - * - * The call context `cc` records whether the node is reached through an - * argument in a call, and if so, `summaryCtx` and `argAp` record the - * corresponding parameter position and access path of that argument, respectively. - */ - pragma[nomagic] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa, config) and - PrevStage::revFlow(node, state, apa, config) and - filter(node, state, ap, config) - } - - pragma[inline] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, cc, summaryCtx, argAp, ap, _, config) - } - - pragma[nomagic] - private predicate fwdFlow0( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - sourceNode(node, state, config) and - (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and - argAp = apNone() and - summaryCtx = TParamNodeNone() and - ap = getApNil(node) and - apa = getApprox(ap) - or - exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | - fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0, config) and - localCc = getLocalCc(mid, cc) - | - localStep(mid, state0, node, state, true, _, config, localCc) and - ap = ap0 and - apa = apa0 - or - localStep(mid, state0, node, state, false, ap, config, localCc) and - ap0 instanceof ApNil and - apa = getApprox(ap) - ) - or - exists(NodeEx mid | - fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa, pragma[only_bind_into](config)) and - jumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(mid, state, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(mid, state0, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStateStep(mid, state0, node, state, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - // store - exists(TypedContent tc, Ap ap0 | - fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp, config) and - ap = apCons(tc, ap0) and - apa = getApprox(ap) - ) - or - // read - exists(Ap ap0, Content c | - fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp, config) and - fwdFlowConsCand(ap0, c, ap, config) and - apa = getApprox(ap) - ) - or - // flow into a callable - fwdFlowIn(_, node, state, _, cc, _, _, ap, apa, config) and - if PrevStage::parameterMayFlowThrough(node, apa, config) - then ( - summaryCtx = TParamNodeSome(node.asNode()) and - argAp = apSome(ap) - ) else ( - summaryCtx = TParamNodeNone() and argAp = apNone() - ) - or - // flow out of a callable - exists( - DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, - DataFlowCallable inner - | - fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa, config) and - flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa, config) and - inner = ret.getEnclosingCallable() and - cc = getCallContextReturn(inner, call, innercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - or - // flow through a callable - exists( - DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, ApApprox innerArgApa - | - fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowStore( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(DataFlowType contentType, ApApprox apa1 | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1, config) and - PrevStage::storeStepCand(node1, apa1, tc, node2, contentType, config) and - typecheckStore(ap1, contentType) - ) - } - - /** - * Holds if forward flow with access path `tail` reaches a store of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(TypedContent tc | - fwdFlowStore(_, tail, tc, _, _, _, _, _, config) and - tc.getContent() = c and - cons = apCons(tc, tail) - ) - } - - pragma[nomagic] - private predicate readStepCand( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - PrevStage::readStepCand(node1, c, node2, config) and - apc = projectToHeadContent(c) - } - - bindingset[node1, apc] - pragma[inline_late] - private predicate readStepCand0( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - readStepCand(node1, apc, c, node2, config) - } - - pragma[nomagic] - private predicate fwdFlowRead( - Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(ApHeadContent apc | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap, config) and - apc = getHeadContent(ap) and - readStepCand0(node1, apc, c, node2, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowIn( - DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, - ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa, Configuration config - ) { - exists(ArgNodeEx arg, boolean allowsFieldFlow | - fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa, config) and - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowRetFromArg( - RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, ApApprox argApa, - Ap ap, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - fwdFlow(pragma[only_bind_into](ret), state, ccc, - TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), - pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa), - pragma[only_bind_into](config)) and - kind = ret.getKind() and - parameterFlowThroughAllowed(summaryCtx, kind) and - argApa = getApprox(argAp) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) - ) - } - - pragma[inline] - private predicate fwdFlowThrough0( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, - Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa, config) and - fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp, config) - } - - pragma[nomagic] - private predicate fwdFlowThrough( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa, - config) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` - * and data might flow through the target callable and back out at `call`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered( - DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, - ParamNodeEx p, Ap ap, Configuration config - ) { - exists(ApApprox apa | - fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(p, apa, config) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate storeStepFwd( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config - ) { - fwdFlowStore(node1, ap1, tc, node2, _, _, _, _, config) and - ap2 = apCons(tc, ap1) and - fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _, config) - } - - private predicate readStepFwd( - NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config - ) { - fwdFlowRead(ap1, c, n1, n2, _, _, _, _, config) and - fwdFlowConsCand(ap1, c, ap2, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough0( - DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, - ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, - innerArgApa, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough( - RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, - Ap ap, Configuration config - ) { - exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | - returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa, config) and - pos = ret.getReturnPosition() and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowThroughIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap, - Configuration config - ) { - exists(ApApprox argApa | - flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), - allowsFieldFlow, argApa, pragma[only_bind_into](config)) and - fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa, - pragma[only_bind_into](config)) and - returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap, - pragma[only_bind_into](config)) and - if allowsFieldFlow = false then argAp instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowIntoCallAp( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap, - Configuration config - ) { - exists(ApApprox apa | - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - fwdFlow(arg, _, _, _, _, ap, apa, config) - ) - } - - pragma[nomagic] - private predicate flowOutOfCallAp( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, - Ap ap, Configuration config - ) { - exists(ApApprox apa | - flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa, config) and - fwdFlow(ret, _, _, _, _, ap, apa, config) and - pos = ret.getReturnPosition() - ) - } - - /** - * Holds if `node` with access path `ap` is part of a path from a source to a - * sink in the configuration `config`. - * - * The parameter `returnCtx` records whether (and how) the node must be returned - * from the enclosing callable in order to reach a sink, and if so, `returnAp` - * records the access path of the returned value. - */ - pragma[nomagic] - additional predicate revFlow( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - revFlow0(node, state, returnCtx, returnAp, ap, config) and - fwdFlow(node, state, _, _, _, ap, config) - } - - pragma[nomagic] - private predicate revFlow0( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, _, _, _, ap, config) and - sinkNode(node, state, config) and - ( - if hasSinkCallCtx(config) - then returnCtx = TReturnCtxNoFlowThrough() - else returnCtx = TReturnCtxNone() - ) and - returnAp = apNone() and - ap instanceof ApNil - or - exists(NodeEx mid, FlowState state0 | - localStep(node, state, mid, state0, true, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, ap, config) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap, pragma[only_bind_into](config)) and - localStep(node, pragma[only_bind_into](state), mid, state0, false, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, nil, pragma[only_bind_into](config)) and - ap instanceof ApNil - ) - or - exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, state, _, _, ap, config) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStep(node, mid, config) and - revFlow(pragma[only_bind_into](mid), state, _, _, nil, pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStateStep(node, state, mid, state0, config) and - revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil, - pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - // store - exists(Ap ap0, Content c | - revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp, config) and - revFlowConsCand(ap0, c, ap, config) - ) - or - // read - exists(NodeEx mid, Ap ap0 | - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - readStepFwd(node, ap, _, mid, ap0, config) - ) - or - // flow into a callable - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, state, TReturnCtxNone(), returnAp, ap, config) and - flowIntoCallAp(_, node, p, allowsFieldFlow, ap, config) and - (if allowsFieldFlow = false then ap instanceof ApNil else any()) and - returnCtx = TReturnCtxNone() - ) - or - // flow through a callable - exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, node, p, _, ap, innerReturnAp, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(_, node, pos, state, _, _, ap, config) and - if returnFlowsThrough(node, pos, state, _, _, _, ap, config) - then ( - returnCtx = TReturnCtxMaybeFlowThrough(pos) and - returnAp = apSome(ap) - ) else ( - returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() - ) - ) - } - - pragma[nomagic] - private predicate revFlowStore( - Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, - ReturnCtx returnCtx, ApOption returnAp, Configuration config - ) { - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - storeStepFwd(node, ap, tc, mid, ap0, config) and - tc.getContent() = c - } - - /** - * Holds if reverse flow with access path `tail` reaches a read of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(NodeEx mid, Ap tail0 | - revFlow(mid, _, _, _, tail, config) and - tail = pragma[only_bind_into](tail0) and - readStepFwd(_, cons, c, mid, tail0, config) - ) - } - - pragma[nomagic] - private predicate revFlowOut( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, - ApOption returnAp, Ap ap, Configuration config - ) { - exists(NodeEx out, boolean allowsFieldFlow | - revFlow(out, state, returnCtx, returnAp, ap, config) and - flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate revFlowParamToReturn( - ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap, Configuration config - ) { - revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), - pragma[only_bind_into](ap), pragma[only_bind_into](config)) and - parameterFlowThroughAllowed(p, pos.getKind()) and - PrevStage::parameterMayFlowThrough(p, getApprox(ap), config) - } - - pragma[nomagic] - private predicate revFlowThrough( - DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, - ApOption returnAp, Ap ap, Ap innerReturnAp, Configuration config - ) { - revFlowParamToReturn(p, state, pos, innerReturnAp, ap, config) and - revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned( - DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap, - Configuration config - ) { - exists(RetNodeEx ret, FlowState state, CcCall ccc | - revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap, config) and - returnFlowsThrough(ret, pos, state, ccc, _, _, ap, config) and - matchesCall(ccc, call) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Ap ap2, Content c | - PrevStage::storeStepCand(node1, _, tc, node2, contentType, config) and - revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _, config) and - revFlowConsCand(ap2, c, ap1, config) - ) - } - - predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(Ap ap1, Ap ap2 | - revFlow(node2, _, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and - readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _, - pragma[only_bind_into](config)) - ) - } - - additional predicate revFlow(NodeEx node, FlowState state, Configuration config) { - revFlow(node, state, _, _, _, config) - } - - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, _, _, ap, config) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, _, _, _, ap, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, Configuration config) { - revFlow(node, _, _, _, _, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, ap, config) - } - - private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepFwd(_, ap, tc, _, _, config) - } - - private predicate revConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepCand(_, ap, tc, _, _, config) - } - - private predicate validAp(Ap ap, Configuration config) { - revFlow(_, _, _, _, ap, config) and ap instanceof ApNil - or - exists(TypedContent head, Ap tail | - consCand(head, tail, config) and - ap = apCons(head, tail) - ) - } - - additional predicate consCand(TypedContent tc, Ap ap, Configuration config) { - revConsCand(tc, ap, config) and - validAp(ap, config) - } - - pragma[nomagic] - private predicate parameterFlowsThroughRev( - ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp, Configuration config - ) { - revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap, config) and - parameterFlowThroughAllowed(p, pos.getKind()) - } - - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(ReturnPosition pos | - returnFlowsThrough(_, pos, _, _, p, ap, _, config) and - parameterFlowsThroughRev(p, ap, pos, _, config) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - exists(ParamNodeEx p, ReturnPosition pos | - returnFlowsThrough(ret, pos, _, _, p, argAp, ap, config) and - parameterFlowsThroughRev(p, argAp, pos, ap, config) and - kind = pos.getKind() - ) - } - - pragma[nomagic] - private predicate revFlowThroughArg( - DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, - Ap ap, Configuration config - ) { - exists(ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp, config) - ) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | - revFlow(arg, state, returnCtx, returnAp, ap, config) and - revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, config)) and - fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and - states = count(FlowState state | fwdFlow(_, state, _, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | - fwdFlow(n, state, cc, summaryCtx, argAp, ap, config) - ) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, _, _, _, config)) and - fields = count(TypedContent f0 | consCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and - states = count(FlowState state | revFlow(_, state, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | - revFlow(n, state, returnCtx, retAp, ap, config) - ) - } - /* End: Stage logic. */ - } -} - -private module BooleanCallContext { - class Cc extends boolean { - Cc() { this in [true, false] } - } - - class CcCall extends Cc { - CcCall() { this = true } - } - - /** Holds if the call context may be `call`. */ - predicate matchesCall(CcCall cc, DataFlowCall call) { any() } - - class CcNoCall extends Cc { - CcNoCall() { this = false } - } - - Cc ccNone() { result = false } - - CcCall ccSomeCall() { result = true } - - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } -} - -private module Level1CallContext { - class Cc = CallContext; - - class CcCall = CallContextCall; - - pragma[inline] - predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } - - class CcNoCall = CallContextNoCall; - - Cc ccNone() { result instanceof CallContextAny } - - CcCall ccSomeCall() { result instanceof CallContextSomeCall } - - module NoLocalCallContext { - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSiteDispatch(call, c) - then result = TSpecificCall(call) - else result = TSomeCall() - } - } - - module LocalCallContext { - class LocalCc = LocalCallContext; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { - result = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - node.getEnclosingCallable()) - } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() - } - } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { - checkCallContextReturn(innercc, c, call) and - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() - } -} - -private module Stage2Param implements MkStage::StageParam { - private module PrevStage = Stage1; - - class Ap extends boolean { - Ap() { this in [true, false] } - } - - class ApNil extends Ap { - ApNil() { this = false } - } - - bindingset[result, ap] - PrevStage::Ap getApprox(Ap ap) { any() } - - ApNil getApNil(NodeEx node) { Stage1::revFlow(node, _) and exists(result) } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } - - class ApHeadContent = Unit; - - pragma[inline] - ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } - - ApHeadContent projectToHeadContent(Content c) { any() } - - class ApOption = BooleanOption; - - ApOption apNone() { result = TBooleanNone() } - - ApOption apSome(Ap ap) { result = TBooleanSome(ap) } - - import Level1CallContext - import NoLocalCallContext - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - ( - preservesValue = true and - localFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalStateStep(node1, state1, node2, state2, config) - ) and - exists(ap) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand1/6; - - predicate flowIntoCall = flowIntoCallNodeCand1/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - expectsContentEx(node, c) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - PrevStage::revFlowState(state, pragma[only_bind_into](config)) and - exists(ap) and - not stateBarrier(node, state, config) and - ( - notExpectsContent(node) - or - ap = true and - expectsContentCand(node, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage2 implements StageSig { - import MkStage::Stage -} - -pragma[nomagic] -private predicate flowOutOfCallNodeCand2( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -pragma[nomagic] -private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -private module LocalFlowBigStep { - /** - * A node where some checking is required, and hence the big-step relation - * is not allowed to step over. - */ - private class FlowCheckNode extends NodeEx { - FlowCheckNode() { - castNode(this.asNode()) or - clearsContentCached(this.asNode(), _) or - expectsContentCached(this.asNode(), _) - } - } - - /** - * Holds if `node` can be the first node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowEntry(NodeEx node, FlowState state, Configuration config) { - Stage2::revFlow(node, state, config) and - ( - sourceNode(node, state, config) - or - jumpStep(_, node, config) - or - additionalJumpStep(_, node, config) - or - additionalJumpStateStep(_, _, node, state, config) - or - node instanceof ParamNodeEx - or - node.asNode() instanceof OutNodeExt - or - Stage2::storeStepCand(_, _, _, node, _, config) - or - Stage2::readStepCand(_, _, node, config) - or - node instanceof FlowCheckNode - or - exists(FlowState s | - additionalLocalStateStep(_, s, node, state, config) and - s != state - ) - ) - } - - /** - * Holds if `node` can be the last node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowExit(NodeEx node, FlowState state, Configuration config) { - exists(NodeEx next | Stage2::revFlow(next, state, config) | - jumpStep(node, next, config) or - additionalJumpStep(node, next, config) or - flowIntoCallNodeCand2(_, node, next, _, config) or - flowOutOfCallNodeCand2(_, node, _, next, _, config) or - Stage2::storeStepCand(node, _, _, next, _, config) or - Stage2::readStepCand(node, _, next, config) - ) - or - exists(NodeEx next, FlowState s | Stage2::revFlow(next, s, config) | - additionalJumpStateStep(node, state, next, s, config) - or - additionalLocalStateStep(node, state, next, s, config) and - s != state - ) - or - Stage2::revFlow(node, state, config) and - node instanceof FlowCheckNode - or - sinkNode(node, state, config) - } - - pragma[noinline] - private predicate additionalLocalFlowStepNodeCand2( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, Configuration config - ) { - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 and - Stage2::revFlow(node1, pragma[only_bind_into](state1), false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false, - pragma[only_bind_into](config)) - or - additionalLocalStateStep(node1, state1, node2, state2, config) and - Stage2::revFlow(node1, state1, false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, state2, false, pragma[only_bind_into](config)) - } - - /** - * Holds if the local path from `node1` to `node2` is a prefix of a maximal - * subsequence of local flow steps in a dataflow path. - * - * This is the transitive closure of `[additional]localFlowStep` beginning - * at `localFlowEntry`. - */ - pragma[nomagic] - private predicate localFlowStepPlus( - NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, - Configuration config, LocalCallContext cc - ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and - ( - localFlowEntry(node1, pragma[only_bind_into](state), pragma[only_bind_into](config)) and - ( - localFlowStepNodeCand1(node1, node2, config) and - preservesValue = true and - t = node1.getDataFlowType() and // irrelevant dummy value - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - or - additionalLocalFlowStepNodeCand2(node1, state, node2, state, config) and - preservesValue = false and - t = node2.getDataFlowType() - ) and - node1 != node2 and - cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) - or - exists(NodeEx mid | - localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, - pragma[only_bind_into](config), cc) and - localFlowStepNodeCand1(mid, node2, config) and - not mid instanceof FlowCheckNode and - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - ) - or - exists(NodeEx mid | - localFlowStepPlus(node1, state, mid, _, _, pragma[only_bind_into](config), cc) and - additionalLocalFlowStepNodeCand2(mid, state, node2, state, config) and - not mid instanceof FlowCheckNode and - preservesValue = false and - t = node2.getDataFlowType() - ) - ) - } - - /** - * Holds if `node1` can step to `node2` in one or more local steps and this - * path can occur as a maximal subsequence of local steps in a dataflow path. - */ - pragma[nomagic] - predicate localFlowBigStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - DataFlowType t, Configuration config, LocalCallContext callContext - ) { - localFlowStepPlus(node1, state1, node2, preservesValue, t, config, callContext) and - localFlowExit(node2, state1, config) and - state1 = state2 - or - additionalLocalFlowStepNodeCand2(node1, state1, node2, state2, config) and - state1 != state2 and - preservesValue = false and - t = node2.getDataFlowType() and - callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) - } -} - -private import LocalFlowBigStep - -private module Stage3Param implements MkStage::StageParam { - private module PrevStage = Stage2; - - class Ap = ApproxAccessPathFront; - - class ApNil = ApproxAccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } - - class ApHeadContent = ContentApprox; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - predicate projectToHeadContent = getContentApprox/1; - - class ApOption = ApproxAccessPathFrontOption; - - ApOption apNone() { result = TApproxAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } - - import BooleanCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApproxAccessPathFrontNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand2/6; - - predicate flowIntoCall = flowIntoCallNodeCand2/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getAHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage3 implements StageSig { - import MkStage::Stage -} - -private module Stage4Param implements MkStage::StageParam { - private module PrevStage = Stage3; - - class Ap = AccessPathFront; - - class ApNil = AccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathFrontOption; - - ApOption apNone() { result = TAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } - - import BooleanCallContext - - pragma[nomagic] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) and - exists(lcc) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clearSet(NodeEx node, ContentSet c, Configuration config) { - PrevStage::revFlow(node, config) and - clearsContentCached(node.asNode(), c) - } - - pragma[nomagic] - private predicate clearContent(NodeEx node, Content c, Configuration config) { - exists(ContentSet cs | - PrevStage::readStepCand(_, pragma[only_bind_into](c), _, pragma[only_bind_into](config)) and - c = cs.getAReadContent() and - clearSet(node, cs, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clear(NodeEx node, Ap ap, Configuration config) { - clearContent(node, ap.getHead().getContent(), config) - } - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - not clear(node, ap, config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage4 implements StageSig { - import MkStage::Stage -} - -/** - * Holds if `argApf` is recorded as the summary context for flow reaching `node` - * and remains relevant for the following pruning stage. - */ -private predicate flowCandSummaryCtx( - NodeEx node, FlowState state, AccessPathFront argApf, Configuration config -) { - exists(AccessPathFront apf | - Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf, config) and - Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf, - config) - ) -} - -/** - * Holds if a length 2 access path approximation with the head `tc` is expected - * to be expensive. - */ -private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { - exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf, config)) and - nodes = - strictcount(NodeEx n, FlowState state | - Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - or - flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - ) and - accessPathApproxCostLimits(apLimit, tupleLimit) and - apLimit < tails and - tupleLimit < (tails - 1) * nodes and - not tc.forceHighPrecision() - ) -} - -private newtype TAccessPathApprox = - TNil(DataFlowType t) or - TConsNil(TypedContent tc, DataFlowType t) { - Stage4::consCand(tc, TFrontNil(t), _) and - not expensiveLen2unfolding(tc, _) - } or - TConsCons(TypedContent tc1, TypedContent tc2, int len) { - Stage4::consCand(tc1, TFrontHead(tc2), _) and - len in [2 .. accessPathLimit()] and - not expensiveLen2unfolding(tc1, _) - } or - TCons1(TypedContent tc, int len) { - len in [1 .. accessPathLimit()] and - expensiveLen2unfolding(tc, _) - } - -/** - * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only - * the first two elements of the list and its length are tracked. If data flows - * from a source to a given node with a given `AccessPathApprox`, this indicates - * the sequence of dereference operations needed to get from the value in the node - * to the tracked object. The final type indicates the type of the tracked object. - */ -abstract private class AccessPathApprox extends TAccessPathApprox { - abstract string toString(); - - abstract TypedContent getHead(); - - abstract int len(); - - abstract DataFlowType getType(); - - abstract AccessPathFront getFront(); - - /** Gets the access path obtained by popping `head` from this path, if any. */ - abstract AccessPathApprox pop(TypedContent head); -} - -private class AccessPathApproxNil extends AccessPathApprox, TNil { - private DataFlowType t; - - AccessPathApproxNil() { this = TNil(t) } - - override string toString() { result = concat(": " + ppReprType(t)) } - - override TypedContent getHead() { none() } - - override int len() { result = 0 } - - override DataFlowType getType() { result = t } - - override AccessPathFront getFront() { result = TFrontNil(t) } - - override AccessPathApprox pop(TypedContent head) { none() } -} - -abstract private class AccessPathApproxCons extends AccessPathApprox { } - -private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { - private TypedContent tc; - private DataFlowType t; - - AccessPathApproxConsNil() { this = TConsNil(tc, t) } - - override string toString() { - // The `concat` becomes "" if `ppReprType` has no result. - result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) - } - - override TypedContent getHead() { result = tc } - - override int len() { result = 1 } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } -} - -private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { - private TypedContent tc1; - private TypedContent tc2; - private int len; - - AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - - override string toString() { - if len = 2 - then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" - else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc1 } - - override int len() { result = len } - - override DataFlowType getType() { result = tc1.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc1) } - - override AccessPathApprox pop(TypedContent head) { - head = tc1 and - ( - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - } -} - -private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { - private TypedContent tc; - private int len; - - AccessPathApproxCons1() { this = TCons1(tc, len) } - - override string toString() { - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc } - - override int len() { result = len } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { - head = tc and - ( - exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2), _) | - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - or - exists(DataFlowType t | - len = 1 and - Stage4::consCand(tc, TFrontNil(t), _) and - result = TNil(t) - ) - ) - } -} - -/** Gets the access path obtained by popping `tc` from `ap`, if any. */ -private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } - -/** Gets the access path obtained by pushing `tc` onto `ap`. */ -private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } - -private newtype TAccessPathApproxOption = - TAccessPathApproxNone() or - TAccessPathApproxSome(AccessPathApprox apa) - -private class AccessPathApproxOption extends TAccessPathApproxOption { - string toString() { - this = TAccessPathApproxNone() and result = "" - or - this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) - } -} - -private module Stage5Param implements MkStage::StageParam { - private module PrevStage = Stage4; - - class Ap = AccessPathApprox; - - class ApNil = AccessPathApproxNil; - - pragma[nomagic] - PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathApproxOption; - - ApOption apNone() { result = TAccessPathApproxNone() } - - ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } - - import Level1CallContext - import LocalCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, lcc) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { any() } - - // Type checking is not necessary here as it has already been done in stage 3. - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage5 = MkStage::Stage; - -bindingset[conf, result] -private Configuration unbindConf(Configuration conf) { - exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c)) -} - -pragma[nomagic] -private predicate nodeMayUseSummary0( - NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(AccessPathApprox apa0 | - Stage5::parameterMayFlowThrough(p, _, _) and - Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0, config) and - Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), - TAccessPathApproxSome(apa), apa0, config) - ) -} - -pragma[nomagic] -private predicate nodeMayUseSummary( - NodeEx n, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::parameterMayFlowThrough(p, apa, config) and - nodeMayUseSummary0(n, p, state, apa, config) - ) -} - -private newtype TSummaryCtx = - TSummaryCtxNone() or - TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { - exists(Configuration config | - Stage5::parameterMayFlowThrough(p, ap.getApprox(), config) and - Stage5::revFlow(p, state, _, config) - ) - } - -/** - * A context for generating flow summaries. This represents flow entry through - * a specific parameter with an access path of a specific shape. - * - * Summaries are only created for parameters that may flow through. - */ -abstract private class SummaryCtx extends TSummaryCtx { - abstract string toString(); -} - -/** A summary context from which no flow summary can be generated. */ -private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { - override string toString() { result = "" } -} - -/** A summary context from which a flow summary can be generated. */ -private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParamNodeEx p; - private FlowState s; - private AccessPath ap; - - SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } - - ParameterPosition getParameterPos() { p.isParameterOf(_, result) } - - ParamNodeEx getParamNode() { result = p } - - override string toString() { result = p + ": " + ap } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** - * Gets the number of length 2 access path approximations that correspond to `apa`. - */ -private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { - exists(TypedContent tc, int len | - tc = apa.getHead() and - len = apa.len() and - result = - strictcount(AccessPathFront apf | - Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), - config) - ) - ) -} - -private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = - strictcount(NodeEx n, FlowState state | - Stage5::revFlow(n, state, apa, config) or nodeMayUseSummary(n, state, apa, config) - ) -} - -/** - * Holds if a length 2 access path approximation matching `apa` is expected - * to be expensive. - */ -private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = count1to2unfold(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - apLimit < aps and - tupleLimit < (aps - 1) * nodes - ) -} - -private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { - exists(TypedContent head | - apa.pop(head) = result and - Stage5::consCand(head, result, config) - ) -} - -/** - * Holds with `unfold = false` if a precise head-tail representation of `apa` is - * expected to be expensive. Holds with `unfold = true` otherwise. - */ -private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { - if apa.getHead().forceHighPrecision() - then unfold = true - else - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = countPotentialAps(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true - ) -} - -/** - * Gets the number of `AccessPath`s that correspond to `apa`. - */ -pragma[assume_small_delta] -private int countAps(AccessPathApprox apa, Configuration config) { - evalUnfold(apa, false, config) and - result = 1 and - (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) - or - evalUnfold(apa, false, config) and - result = count1to2unfold(apa, config) and - not expensiveLen1to2unfolding(apa, config) - or - evalUnfold(apa, true, config) and - result = countPotentialAps(apa, config) -} - -/** - * Gets the number of `AccessPath`s that would correspond to `apa` assuming - * that it is expanded to a precise head-tail representation. - */ -language[monotonicAggregates] -pragma[assume_small_delta] -private int countPotentialAps(AccessPathApprox apa, Configuration config) { - apa instanceof AccessPathApproxNil and result = 1 - or - result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) -} - -private newtype TAccessPath = - TAccessPathNil(DataFlowType t) or - TAccessPathCons(TypedContent head, AccessPath tail) { - exists(AccessPathApproxCons apa | - not evalUnfold(apa, false, _) and - head = apa.getHead() and - tail.getApprox() = getATail(apa, _) - ) - } or - TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - not expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head1 = apa.getHead() and - head2 = getATail(apa, _).getHead() - ) - } or - TAccessPathCons1(TypedContent head, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head = apa.getHead() - ) - } - -private newtype TPathNode = - pragma[assume_small_delta] - TPathNodeMid( - NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config - ) { - // A PathNode is introduced by a source ... - Stage5::revFlow(node, state, config) and - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - or - // ... or a step from an existing PathNode to another node. - exists(PathNodeMid mid | - pathStep(mid, node, state, cc, sc, ap) and - pragma[only_bind_into](config) = mid.getConfiguration() and - Stage5::revFlow(node, state, ap.getApprox(), pragma[only_bind_into](config)) - ) - } or - TPathNodeSink(NodeEx node, FlowState state, Configuration config) { - exists(PathNodeMid sink | - sink.isAtSink() and - node = sink.getNodeEx() and - state = sink.getState() and - config = sink.getConfiguration() - ) - } or - TPathNodeSourceGroup(string sourceGroup, Configuration config) { - exists(PathNodeImpl source | - sourceGroup = source.getSourceGroup() and - config = source.getConfiguration() - ) - } or - TPathNodeSinkGroup(string sinkGroup, Configuration config) { - exists(PathNodeSink sink | - sinkGroup = sink.getSinkGroup() and - config = sink.getConfiguration() - ) - } - -/** - * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a - * source to a given node with a given `AccessPath`, this indicates the sequence - * of dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ -private class AccessPath extends TAccessPath { - /** Gets the head of this access path, if any. */ - abstract TypedContent getHead(); - - /** Gets the tail of this access path, if any. */ - abstract AccessPath getTail(); - - /** Gets the front of this access path. */ - abstract AccessPathFront getFront(); - - /** Gets the approximation of this access path. */ - abstract AccessPathApprox getApprox(); - - /** Gets the length of this access path. */ - abstract int length(); - - /** Gets a textual representation of this access path. */ - abstract string toString(); - - /** Gets the access path obtained by popping `tc` from this access path, if any. */ - final AccessPath pop(TypedContent tc) { - result = this.getTail() and - tc = this.getHead() - } - - /** Gets the access path obtained by pushing `tc` onto this access path. */ - final AccessPath push(TypedContent tc) { this = result.pop(tc) } -} - -private class AccessPathNil extends AccessPath, TAccessPathNil { - private DataFlowType t; - - AccessPathNil() { this = TAccessPathNil(t) } - - DataFlowType getType() { result = t } - - override TypedContent getHead() { none() } - - override AccessPath getTail() { none() } - - override AccessPathFrontNil getFront() { result = TFrontNil(t) } - - override AccessPathApproxNil getApprox() { result = TNil(t) } - - override int length() { result = 0 } - - override string toString() { result = concat(": " + ppReprType(t)) } -} - -private class AccessPathCons extends AccessPath, TAccessPathCons { - private TypedContent head; - private AccessPath tail; - - AccessPathCons() { this = TAccessPathCons(head, tail) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { result = tail } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - pragma[assume_small_delta] - override AccessPathApproxCons getApprox() { - result = TConsNil(head, tail.(AccessPathNil).getType()) - or - result = TConsCons(head, tail.getHead(), this.length()) - or - result = TCons1(head, this.length()) - } - - pragma[assume_small_delta] - override int length() { result = 1 + tail.length() } - - private string toStringImpl(boolean needsSuffix) { - exists(DataFlowType t | - tail = TAccessPathNil(t) and - needsSuffix = false and - result = head.toString() + "]" + concat(" : " + ppReprType(t)) - ) - or - result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) - or - exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | - result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true - or - result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false - ) - or - exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | - result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true - or - result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false - ) - } - - override string toString() { - result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" - or - result = "[" + this.toStringImpl(false) - } -} - -private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { - private TypedContent head1; - private TypedContent head2; - private int len; - - AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } - - override TypedContent getHead() { result = head1 } - - override AccessPath getTail() { - Stage5::consCand(head1, result.getApprox(), _) and - result.getHead() = head2 and - result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head1) } - - override AccessPathApproxCons getApprox() { - result = TConsCons(head1, head2, len) or - result = TCons1(head1, len) - } - - override int length() { result = len } - - override string toString() { - if len = 2 - then result = "[" + head1.toString() + ", " + head2.toString() + "]" - else - result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" - } -} - -private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { - private TypedContent head; - private int len; - - AccessPathCons1() { this = TAccessPathCons1(head, len) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { - Stage5::consCand(head, result.getApprox(), _) and result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - - override int length() { result = len } - - override string toString() { - if len = 1 - then result = "[" + head.toString() + "]" - else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" - } -} - -abstract private class PathNodeImpl extends TPathNode { - /** Gets the `FlowState` of this node. */ - abstract FlowState getState(); - - /** Gets the associated configuration. */ - abstract Configuration getConfiguration(); - - /** Holds if this node is a source. */ - abstract predicate isSource(); - - abstract PathNodeImpl getASuccessorImpl(); - - private PathNodeImpl getASuccessorIfHidden() { - this.isHidden() and - result = this.getASuccessorImpl() - } - - pragma[nomagic] - private PathNodeImpl getANonHiddenSuccessor0() { - result = this.getASuccessorIfHidden*() and - not result.isHidden() - } - - final PathNodeImpl getANonHiddenSuccessor() { - result = this.getASuccessorImpl().getANonHiddenSuccessor0() and - not this.isHidden() - } - - abstract NodeEx getNodeEx(); - - predicate isHidden() { - not this.getConfiguration().includeHiddenNodes() and - ( - hiddenNode(this.getNodeEx().asNode()) and - not this.isSource() and - not this instanceof PathNodeSink - or - this.getNodeEx() instanceof TNodeImplicitRead - ) - } - - string getSourceGroup() { - this.isSource() and - this.getConfiguration().sourceGrouping(this.getNodeEx().asNode(), result) + not singleConfiguration() and + getConfig(state1).isAdditionalFlowStep(node1, node2) and + state2 = state1 } - predicate isFlowSource() { - this.isSource() and not exists(this.getSourceGroup()) - or - this instanceof PathNodeSourceGroup + predicate allowImplicitRead(Node node, ContentSet c) { + any(Configuration config).allowImplicitRead(node, c) } - predicate isFlowSink() { - this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or - this instanceof PathNodeSinkGroup - } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } - private string ppAp() { - this instanceof PathNodeSink and result = "" - or - exists(string s | s = this.(PathNodeMid).getAp().toString() | - if s = "" then result = "" else result = " " + s - ) - } + FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } - private string ppCtx() { - this instanceof PathNodeSink and result = "" - or - result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + predicate sourceGrouping(Node source, string sourceGroup) { + any(Configuration config).sourceGrouping(source, sourceGroup) } - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + predicate sinkGrouping(Node sink, string sinkGroup) { + any(Configuration config).sinkGrouping(sink, sinkGroup) } -} - -/** Holds if `n` can reach a sink. */ -private predicate directReach(PathNodeImpl n) { - n instanceof PathNodeSink or - n instanceof PathNodeSinkGroup or - directReach(n.getANonHiddenSuccessor()) -} - -/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ -private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } -/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { - n1.getANonHiddenSuccessor() = n2 and directReach(n2) + predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() } } -private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) +private import Impl as I +import I /** * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. */ -class PathNode instanceof PathNodeImpl { - PathNode() { reach(this) } - +class PathNode instanceof I::PathNode { /** Gets a textual representation of this element. */ final string toString() { result = super.toString() } @@ -3406,1548 +358,39 @@ class PathNode instanceof PathNodeImpl { } /** Gets the underlying `Node`. */ - final Node getNode() { super.getNodeEx().projectToNode() = result } + final Node getNode() { result = super.getNode() } /** Gets the `FlowState` of this node. */ - final FlowState getState() { result = super.getState() } + final FlowState getState() { result = getState(super.getState()) } /** Gets the associated configuration. */ - final Configuration getConfiguration() { result = super.getConfiguration() } + final Configuration getConfiguration() { result = getConfig(super.getState()) } /** Gets a successor of this node, if any. */ - final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } + final PathNode getASuccessor() { result = super.getASuccessor() } /** Holds if this node is a source. */ final predicate isSource() { super.isSource() } /** Holds if this node is a grouping of source nodes. */ - final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group, _) } + final predicate isSourceGroup(string group) { super.isSourceGroup(group) } /** Holds if this node is a grouping of sink nodes. */ - final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group, _) } + final predicate isSinkGroup(string group) { super.isSinkGroup(group) } } -/** - * Provides the query predicates needed to include a graph in a path-problem query. - */ -module PathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } - - /** Holds if `n` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode n, string key, string val) { - key = "semmle.label" and val = n.toString() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { - Subpaths::subpaths(arg, par, ret, out) - } -} - -/** - * An intermediate flow graph node. This is a triple consisting of a `Node`, - * a `CallContext`, and a `Configuration`. - */ -private class PathNodeMid extends PathNodeImpl, TPathNodeMid { - NodeEx node; - FlowState state; - CallContext cc; - SummaryCtx sc; - AccessPath ap; - Configuration config; - - PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - SummaryCtx getSummaryCtx() { result = sc } - - AccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - private PathNodeMid getSuccMid() { - pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx(), result.getAp()) and - result.getConfiguration() = unbindConf(this.getConfiguration()) - } - - override PathNodeImpl getASuccessorImpl() { - // an intermediate step to another intermediate node - result = this.getSuccMid() - or - // a final step to a sink - result = this.getSuccMid().projectToSink() - } - - override predicate isSource() { - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - } - - predicate isAtSink() { - sinkNode(node, state, config) and - ap instanceof AccessPathNil and - if hasSinkCallCtx(config) - then - // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` - // is exactly what we need to check. This also implies - // `sc instanceof SummaryCtxNone`. - // For `FeatureEqualSourceSinkCallContext` the initial call context was - // set to `CallContextSomeCall` and jumps are disallowed, so - // `cc instanceof CallContextNoCall` never holds. On the other hand, - // in this case there's never any need to enter a call except to identify - // a summary, so the condition in `pathIntoCallable` enforces this, which - // means that `sc instanceof SummaryCtxNone` holds if and only if we are - // in the call context of the source. - sc instanceof SummaryCtxNone or - cc instanceof CallContextNoCall - else any() - } - - PathNodeSink projectToSink() { - this.isAtSink() and - result.getNodeEx() = node and - result.getState() = state and - result.getConfiguration() = unbindConf(config) - } -} - -/** - * A flow graph node corresponding to a sink. This is disjoint from the - * intermediate nodes in order to uniquely correspond to a given sink by - * excluding the `CallContext`. - */ -private class PathNodeSink extends PathNodeImpl, TPathNodeSink { - NodeEx node; - FlowState state; - Configuration config; - - PathNodeSink() { this = TPathNodeSink(node, state, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result = TPathNodeSinkGroup(this.getSinkGroup(), config) - } - - override predicate isSource() { sourceNode(node, state, config) } - - string getSinkGroup() { config.sinkGrouping(node.asNode(), result) } -} - -private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { - string sourceGroup; - Configuration config; - - PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result.getSourceGroup() = sourceGroup and - result.getConfiguration() = config - } - - override predicate isSource() { none() } - - override string toString() { result = sourceGroup } - - override 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 - } -} - -private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { - string sinkGroup; - Configuration config; - - PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { none() } - - override predicate isSource() { none() } - - override string toString() { result = sinkGroup } - - override 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 - } -} - -private predicate pathNode( - PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, - Configuration conf, LocalCallContext localCC -) { - midnode = mid.getNodeEx() and - state = mid.getState() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - midnode.getEnclosingCallable()) and - ap = mid.getAp() -} - -/** - * Holds if data may flow from `mid` to `node`. The last step in or out of - * a callable is recorded by `cc`. - */ -pragma[assume_small_delta] -pragma[nomagic] -private predicate pathStep( - PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap -) { - exists(NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC | - pathNode(mid, midnode, state0, cc, sc, ap, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, true, _, conf, localCC) - ) - or - exists( - AccessPath ap0, NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC - | - pathNode(mid, midnode, state0, cc, sc, ap0, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), conf, - localCC) and - ap0 instanceof AccessPathNil - ) - or - jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - ap = mid.getAp() - or - additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, mid.getConfiguration()) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - pathIntoCallable(mid, node, state, _, cc, sc, _, _) and ap = mid.getAp() - or - pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone - or - pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() -} - -pragma[nomagic] -private predicate pathReadStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - tc = ap0.getHead() and - Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -pragma[nomagic] -private predicate pathStoreStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -private predicate pathOutOfCallable0( - PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa, - Configuration config -) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - apa = mid.getAp().getApprox() and - config = mid.getConfiguration() -} - -pragma[nomagic] -private predicate pathOutOfCallable1( - PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPathApprox apa, Configuration config -) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - pathOutOfCallable0(mid, pos, state, innercc, apa, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() +private predicate hasFlow(Node source, Node sink, Configuration config) { + exists(PathNode source0, PathNode sink0 | + hasFlowPath(source0, sink0, config) and + source0.getNode() = source and + sink0.getNode() = sink ) } -pragma[noinline] -private NodeEx getAnOutNodeFlow( - ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config -) { - result.asNode() = kind.getAnOutNode(call) and - Stage5::revFlow(result, _, apa, config) +private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) { + hasFlowPath(source, sink) and source.getConfiguration() = config } -/** - * Holds if data may flow from `mid` to `out`. The last step of this path - * is a return from a callable and is recorded by `cc`, if needed. - */ -pragma[noinline] -private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { - exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | - pathOutOfCallable1(mid, call, kind, state, cc, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} +private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) } -/** - * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. - */ -pragma[noinline] -private predicate pathIntoArg( - PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(ArgNodeEx arg, ArgumentPosition apos | - pathNode(mid, arg, state, cc, _, ap, config, _) and - arg.asNode().(ArgNode).argumentOf(call, apos) and - apa = ap.getApprox() and - parameterMatch(ppos, apos) - ) -} - -pragma[nomagic] -private predicate parameterCand( - DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::revFlow(p, _, apa, config) and - p.isParameterOf(callable, pos) - ) -} - -pragma[nomagic] -private predicate pathIntoCallable0( - PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, AccessPath ap, Configuration config -) { - exists(AccessPathApprox apa | - pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - callable = resolveCall(call, outercc) and - parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa), - pragma[only_bind_into](config)) - ) -} - -/** - * Holds if data may flow from `mid` to `p` through `call`. The contexts - * before and after entering the callable are `outercc` and `innercc`, - * respectively. - */ -pragma[nomagic] -private predicate pathIntoCallable( - PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, - SummaryCtx sc, DataFlowCall call, Configuration config -) { - exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | - pathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - ( - sc = TSummaryCtxSome(p, state, ap) - or - not exists(TSummaryCtxSome(p, state, ap)) and - sc = TSummaryCtxNone() and - // When the call contexts of source and sink needs to match then there's - // never any reason to enter a callable except to find a summary. See also - // the comment in `PathNodeMid::isAtSink`. - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) -} - -/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ -pragma[nomagic] -private predicate paramFlowsThrough( - ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, - AccessPathApprox apa, Configuration config -) { - exists(RetNodeEx ret | - pathNode(_, ret, state, cc, sc, ap, config, _) and - kind = ret.getKind() and - apa = ap.getApprox() and - parameterFlowThroughAllowed(sc.getParamNode(), kind) - ) -} - -pragma[nomagic] -private predicate pathThroughCallable0( - DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(CallContext innercc, SummaryCtx sc | - pathIntoCallable(mid, _, _, cc, innercc, sc, call, config) and - paramFlowsThrough(kind, state, innercc, sc, ap, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` through a callable to the node `out`. - * The context `cc` is restored to its value prior to entering the callable. - */ -pragma[noinline] -private predicate pathThroughCallable( - PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap -) { - exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config | - pathThroughCallable0(call, mid, kind, state, cc, ap, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -private module Subpaths { - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths01( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - exists(Configuration config | - pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and - pathIntoCallable(arg, par, _, _, innercc, sc, _, config) and - paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, - pragma[only_bind_into](apout), _, unbindConf(config)) and - not arg.isHidden() - ) - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `sout`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths02( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and - out.asNode() = kind.getAnOutNode(_) - } - - pragma[nomagic] - private Configuration getPathNodeConf(PathNodeImpl n) { result = n.getConfiguration() } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple. - */ - pragma[nomagic] - private predicate subpaths03( - PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, AccessPath apout - ) { - exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | - subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and - pathNode(ret, retnode, sout, innercc, sc, apout, unbindConf(getPathNodeConf(arg)), _) and - kind = retnode.getKind() - ) - } - - private PathNodeImpl localStepToHidden(PathNodeImpl n) { - n.getASuccessorImpl() = result and - result.isHidden() and - exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | - localFlowBigStep(n1, _, n2, _, _, _, _, _) or - store(n1, _, n2, _, _) or - readSet(n1, _, n2, _) - ) - } - - pragma[nomagic] - private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { - succ = pred.getANonHiddenSuccessor() and - succNode = succ.getNodeEx() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { - exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | - pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and - subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and - hasSuccessor(pragma[only_bind_into](arg), par, p) and - not ret.isHidden() and - pathNode(out0, o, sout, _, _, apout, _, _) - | - out = out0 or out = out0.projectToSink() - ) - } - - /** - * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. - */ - predicate retReach(PathNodeImpl n) { - exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) - or - exists(PathNodeImpl mid | - retReach(mid) and - n.getANonHiddenSuccessor() = mid and - not subpaths(_, mid, _, _) - ) - } -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -private predicate hasFlowPath( - PathNodeImpl flowsource, PathNodeImpl flowsink, Configuration configuration -) { - flowsource.isFlowSource() and - flowsource.getConfiguration() = configuration and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.isFlowSink() -} - -private predicate flowsTo( - PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink, - Configuration configuration -) { - flowsource.isSource() and - flowsource.getConfiguration() = configuration and - flowsource.getNodeEx().asNode() = source and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.getNodeEx().asNode() = sink -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -predicate flowsTo(Node source, Node sink, Configuration configuration) { - flowsTo(_, _, source, sink, configuration) -} - -private predicate finalStats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples -) { - fwd = true and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and - tuples = count(PathNodeImpl pn) - or - fwd = false and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and - tuples = count(PathNode pn) -} - -/** - * INTERNAL: Only for debugging. - * - * Calculates per-stage metrics for data flow. - */ -predicate stageStats( - int n, string stage, int nodes, int fields, int conscand, int states, int tuples, - Configuration config -) { - stage = "1 Fwd" and - n = 10 and - Stage1::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "1 Rev" and - n = 15 and - Stage1::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Fwd" and - n = 20 and - Stage2::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Rev" and - n = 25 and - Stage2::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Fwd" and - n = 30 and - Stage3::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Rev" and - n = 35 and - Stage3::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Fwd" and - n = 40 and - Stage4::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Rev" and - n = 45 and - Stage4::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Fwd" and - n = 50 and - Stage5::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Rev" and - n = 55 and - Stage5::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) - or - stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) -} - -private module FlowExploration { - private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - jumpStep(node1, node2, config) - or - additionalJumpStep(node1, node2, config) - or - additionalJumpStateStep(node1, _, node2, _, config) - or - // flow into callable - viableParamArgEx(_, node2, node1) - or - // flow out of a callable - viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) - | - c1 = node1.getEnclosingCallable() and - c2 = node2.getEnclosingCallable() and - c1 != c2 - ) - } - - private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSource(n) or config.isSource(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSrc(mid, config) and callableStep(mid, c, config) - ) - } - - private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSink(n) or config.isSink(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSink(mid, config) and callableStep(c, mid, config) - ) - } - - private newtype TCallableExt = - TCallable(DataFlowCallable c, Configuration config) { - interestingCallableSrc(c, config) or - interestingCallableSink(c, config) - } or - TCallableSrc() or - TCallableSink() - - private predicate callableExtSrc(TCallableSrc src) { any() } - - private predicate callableExtSink(TCallableSink sink) { any() } - - private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { - exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | - callableStep(c1, c2, config) and - ce1 = TCallable(c1, pragma[only_bind_into](config)) and - ce2 = TCallable(c2, pragma[only_bind_into](config)) - ) - or - exists(Node n, Configuration config | - ce1 = TCallableSrc() and - (config.isSource(n) or config.isSource(n, _)) and - ce2 = TCallable(getNodeEnclosingCallable(n), config) - ) - or - exists(Node n, Configuration config | - ce2 = TCallableSink() and - (config.isSink(n) or config.isSink(n, _)) and - ce1 = TCallable(getNodeEnclosingCallable(n), config) - ) - } - - private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { - callableExtStepFwd(ce2, ce1) - } - - private int distSrcExt(TCallableExt c) = - shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) - - private int distSinkExt(TCallableExt c) = - shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) - - private int distSrc(DataFlowCallable c, Configuration config) { - result = distSrcExt(TCallable(c, config)) - 1 - } - - private int distSink(DataFlowCallable c, Configuration config) { - result = distSinkExt(TCallable(c, config)) - 1 - } - - private newtype TPartialAccessPath = - TPartialNil(DataFlowType t) or - TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first - * element of the list and its length are tracked. If data flows from a source to - * a given node with a given `AccessPath`, this indicates the sequence of - * dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ - private class PartialAccessPath extends TPartialAccessPath { - abstract string toString(); - - TypedContent getHead() { this = TPartialCons(result, _) } - - int len() { - this = TPartialNil(_) and result = 0 - or - this = TPartialCons(_, result) - } - - DataFlowType getType() { - this = TPartialNil(result) - or - exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) - } - } - - private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { - override string toString() { - exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) - } - } - - private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { - override string toString() { - exists(TypedContent tc, int len | this = TPartialCons(tc, len) | - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private newtype TRevPartialAccessPath = - TRevPartialNil() or - TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `Content`s, but only the first - * element of the list and its length are tracked. - */ - private class RevPartialAccessPath extends TRevPartialAccessPath { - abstract string toString(); - - Content getHead() { this = TRevPartialCons(result, _) } - - int len() { - this = TRevPartialNil() and result = 0 - or - this = TRevPartialCons(_, result) - } - } - - private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { - override string toString() { result = "" } - } - - private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { - override string toString() { - exists(Content c, int len | this = TRevPartialCons(c, len) | - if len = 1 - then result = "[" + c.toString() + "]" - else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private predicate relevantState(FlowState state) { - sourceNode(_, state, _) or - sinkNode(_, state, _) or - additionalLocalStateStep(_, state, _, _, _) or - additionalLocalStateStep(_, _, _, state, _) or - additionalJumpStateStep(_, state, _, _, _) or - additionalJumpStateStep(_, _, _, state, _) - } - - private newtype TSummaryCtx1 = - TSummaryCtx1None() or - TSummaryCtx1Param(ParamNodeEx p) - - private newtype TSummaryCtx2 = - TSummaryCtx2None() or - TSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TSummaryCtx3 = - TSummaryCtx3None() or - TSummaryCtx3Some(PartialAccessPath ap) - - private newtype TRevSummaryCtx1 = - TRevSummaryCtx1None() or - TRevSummaryCtx1Some(ReturnPosition pos) - - private newtype TRevSummaryCtx2 = - TRevSummaryCtx2None() or - TRevSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TRevSummaryCtx3 = - TRevSummaryCtx3None() or - TRevSummaryCtx3Some(RevPartialAccessPath ap) - - private newtype TPartialPathNode = - TPartialPathNodeFwd( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = TPartialNil(node.getDataFlowType()) and - exists(config.explorationLimit()) - or - partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap, config) and - distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() - } or - TPartialPathNodeRev( - NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, - RevPartialAccessPath ap, Configuration config - ) { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() and - exists(config.explorationLimit()) - or - revPartialPathStep(_, node, state, sc1, sc2, sc3, ap, config) and - not clearsContentEx(node, ap.getHead()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead()) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() - } - - pragma[nomagic] - private predicate partialPathNodeMk0( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap, config) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - not clearsContentEx(node, ap.getHead().getContent()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead().getContent()) - ) and - if node.asNode() instanceof CastingNode - then compatibleTypes(node.getDataFlowType(), ap.getType()) - else any() - } - - /** - * A `Node` augmented with a call context, an access path, and a configuration. - */ - class PartialPathNode extends TPartialPathNode { - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() - } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - /** Gets the underlying `Node`. */ - final Node getNode() { this.getNodeEx().projectToNode() = result } - - FlowState getState() { none() } - - private NodeEx getNodeEx() { - result = this.(PartialPathNodeFwd).getNodeEx() or - result = this.(PartialPathNodeRev).getNodeEx() - } - - /** Gets the associated configuration. */ - Configuration getConfiguration() { none() } - - /** Gets a successor of this node, if any. */ - PartialPathNode getASuccessor() { none() } - - /** - * Gets the approximate distance to the nearest source measured in number - * of interprocedural steps. - */ - int getSourceDistance() { - result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - /** - * Gets the approximate distance to the nearest sink measured in number - * of interprocedural steps. - */ - int getSinkDistance() { - result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - private string ppAp() { - exists(string s | - s = this.(PartialPathNodeFwd).getAp().toString() or - s = this.(PartialPathNodeRev).getAp().toString() - | - if s = "" then result = "" else result = " " + s - ) - } - - private string ppCtx() { - result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" - } - - /** Holds if this is a source in a forward-flow path. */ - predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } - - /** Holds if this is a sink in a reverse-flow path. */ - predicate isRevSink() { this.(PartialPathNodeRev).isSink() } - } - - /** - * Provides the query predicates needed to include a graph in a path-problem query. - */ - module PartialPathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } - } - - private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { - NodeEx node; - FlowState state; - CallContext cc; - TSummaryCtx1 sc1; - TSummaryCtx2 sc2; - TSummaryCtx3 sc3; - PartialAccessPath ap; - Configuration config; - - PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - TSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TSummaryCtx3 getSummaryCtx3() { result = sc3 } - - PartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeFwd getASuccessor() { - partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp(), - result.getConfiguration()) - } - - predicate isSource() { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap instanceof TPartialNil - } - } - - private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { - NodeEx node; - FlowState state; - TRevSummaryCtx1 sc1; - TRevSummaryCtx2 sc2; - TRevSummaryCtx3 sc3; - RevPartialAccessPath ap; - Configuration config; - - PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } - - RevPartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeRev getASuccessor() { - revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), - this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp(), this.getConfiguration()) - } - - predicate isSink() { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() - } - } - - private predicate partialPathStep( - PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, - TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and - ( - localFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - ) - or - jumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - partialPathStoreStep(mid, _, _, node, ap) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(PartialAccessPath ap0, TypedContent tc | - partialPathReadStep(mid, ap0, tc, node, cc, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsFwd(ap, tc, ap0, config) - ) - or - partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap, config) - or - partialPathOutOfCallable(mid, node, state, cc, ap, config) and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() - or - partialPathThroughCallable(mid, node, state, cc, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - bindingset[result, i] - private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } - - pragma[inline] - private predicate partialPathStoreStep( - PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, - PartialAccessPath ap2 - ) { - exists(NodeEx midNode, DataFlowType contentType | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - store(midNode, tc, node, contentType, mid.getConfiguration()) and - ap2.getHead() = tc and - ap2.len() = unbindInt(ap1.len() + 1) and - compatibleTypes(ap1.getType(), contentType) - ) - } - - pragma[nomagic] - private predicate apConsFwd( - PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeFwd mid | - partialPathStoreStep(mid, ap1, tc, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate partialPathReadStep( - PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc, - Configuration config - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and - ap.getHead() = tc and - pragma[only_bind_into](config) = mid.getConfiguration() and - cc = mid.getCallContext() - ) - } - - private predicate partialPathOutOfCallable0( - PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, - PartialAccessPath ap, Configuration config - ) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - ap = mid.getAp() and - config = mid.getConfiguration() - } - - pragma[nomagic] - private predicate partialPathOutOfCallable1( - PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - partialPathOutOfCallable0(mid, pos, state, innercc, ap, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) - } - - private predicate partialPathOutOfCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(ReturnKindExt kind, DataFlowCall call | - partialPathOutOfCallable1(mid, call, kind, state, cc, ap, config) - | - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[noinline] - private predicate partialPathIntoArg( - PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ArgNode arg, ArgumentPosition apos | - arg = mid.getNodeEx().asNode() and - state = mid.getState() and - cc = mid.getCallContext() and - arg.argumentOf(call, apos) and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate partialPathIntoCallable0( - PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - partialPathIntoArg(mid, pos, state, outercc, call, ap, config) and - callable = resolveCall(call, outercc) - } - - private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, - CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ParameterPosition pos, DataFlowCallable callable | - partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - sc1 = TSummaryCtx1Param(p) and - sc2 = TSummaryCtx2Some(state) and - sc3 = TSummaryCtx3Some(ap) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) - } - - pragma[nomagic] - private predicate paramFlowsThroughInPartialPath( - ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeFwd mid, RetNodeEx ret | - mid.getNodeEx() = ret and - kind = ret.getKind() and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() and - ap = mid.getAp() - ) - } - - pragma[noinline] - private predicate partialPathThroughCallable0( - DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | - partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _, config) and - paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap, config) - ) - } - - private predicate partialPathThroughCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ReturnKindExt kind | - partialPathThroughCallable0(call, mid, kind, state, cc, ap, config) and - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[nomagic] - private predicate revPartialPathStep( - PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, - TRevSummaryCtx3 sc3, RevPartialAccessPath ap, Configuration config - ) { - localFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - jumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - revPartialPathReadStep(mid, _, _, node, ap) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(RevPartialAccessPath ap0, Content c | - revPartialPathStoreStep(mid, ap0, c, node, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsRev(ap, c, ap0, config) - ) - or - exists(ParamNodeEx p | - mid.getNodeEx() = p and - viableParamArgEx(_, p, node) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - or - exists(ReturnPosition pos | - revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap, config) and - pos = getReturnPosition(node.asNode()) - ) - or - revPartialPathThroughCallable(mid, node, state, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - pragma[inline] - private predicate revPartialPathReadStep( - PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, - RevPartialAccessPath ap2 - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - read(node, c, midNode, mid.getConfiguration()) and - ap2.getHead() = c and - ap2.len() = unbindInt(ap1.len() + 1) - ) - } - - pragma[nomagic] - private predicate apConsRev( - RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeRev mid | - revPartialPathReadStep(mid, ap1, c, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathStoreStep( - PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config - ) { - exists(NodeEx midNode, TypedContent tc | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - store(node, tc, midNode, _, config) and - ap.getHead() = c and - config = mid.getConfiguration() and - tc.getContent() = c - ) - } - - pragma[nomagic] - private predicate revPartialPathIntoReturn( - PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, - TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap, - Configuration config - ) { - exists(NodeEx out | - mid.getNodeEx() = out and - mid.getState() = state and - viableReturnPosOutEx(call, pos, out) and - sc1 = TRevSummaryCtx1Some(pos) and - sc2 = TRevSummaryCtx2Some(state) and - sc3 = TRevSummaryCtx3Some(ap) and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathFlowsThrough( - ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, - TRevSummaryCtx3Some sc3, RevPartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | - mid.getNodeEx() = p and - mid.getState() = state and - p.getPosition() = ppos and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable0( - DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, - RevPartialAccessPath ap, Configuration config - ) { - exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | - revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _, config) and - revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap, config) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ArgumentPosition pos | - revPartialPathThroughCallable0(call, mid, pos, state, ap, config) and - node.asNode().(ArgNode).argumentOf(call, pos) - ) - } -} - -import FlowExploration - -private predicate partialFlow( - PartialPathNode source, PartialPathNode node, Configuration configuration -) { - source.getConfiguration() = configuration and - source.isFwdSource() and - node = source.getASuccessor+() -} - -private predicate revPartialFlow( - PartialPathNode node, PartialPathNode sink, Configuration configuration -) { - sink.getConfiguration() = configuration and - sink.isRevSink() and - node.getASuccessor+() = sink -} +predicate flowsTo = hasFlow/3; diff --git a/go/ql/lib/semmle/go/dataflow/internal/DataFlowImplCommon.qll b/go/ql/lib/semmle/go/dataflow/internal/DataFlowImplCommon.qll index 5d3becc8078..f8c56d41d8d 100644 --- a/go/ql/lib/semmle/go/dataflow/internal/DataFlowImplCommon.qll +++ b/go/ql/lib/semmle/go/dataflow/internal/DataFlowImplCommon.qll @@ -3,15 +3,18 @@ private import DataFlowImplSpecific::Public import Cached module DataFlowImplCommonPublic { - /** A state value to track during data flow. */ - class FlowState = string; + /** Provides `FlowState = string`. */ + module FlowStateString { + /** A state value to track during data flow. */ + class FlowState = string; - /** - * The default state, which is used when the state is unspecified for a source - * or a sink. - */ - class FlowStateEmpty extends FlowState { - FlowStateEmpty() { this = "" } + /** + * The default state, which is used when the state is unspecified for a source + * or a sink. + */ + class FlowStateEmpty extends FlowState { + FlowStateEmpty() { this = "" } + } } private newtype TFlowFeature = diff --git a/go/ql/lib/semmle/go/dataflow/internal/DataFlowImplForStringsNewReplacer.qll b/go/ql/lib/semmle/go/dataflow/internal/DataFlowImplForStringsNewReplacer.qll index 1b969756b09..e6bdc74cceb 100644 --- a/go/ql/lib/semmle/go/dataflow/internal/DataFlowImplForStringsNewReplacer.qll +++ b/go/ql/lib/semmle/go/dataflow/internal/DataFlowImplForStringsNewReplacer.qll @@ -1,16 +1,16 @@ /** - * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from - * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed - * through the `Configuration` class. This file exists in several identical - * copies, allowing queries to use multiple `Configuration` classes that depend - * on each other without introducing mutual recursion among those configurations. + * DEPRECATED: Use `Make` and `MakeWithState` instead. + * + * Provides a `Configuration` class backwards-compatible interface to the data + * flow library. */ private import DataFlowImplCommon private import DataFlowImplSpecific::Private import DataFlowImplSpecific::Public +private import DataFlowImpl import DataFlowImplCommonPublic +import FlowStateString /** * A configuration of interprocedural data flow analysis. This defines @@ -144,6 +144,10 @@ abstract class Configuration extends string { * - `FeatureEqualSourceSinkCallContext`: * Implies both of the above and additionally ensures that the entire flow * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. */ FlowFeature getAFeature() { none() } @@ -156,7 +160,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from `source` to `sink` for this configuration. */ - predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } + predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) } /** * Holds if data may flow from `source` to `sink` for this configuration. @@ -169,9 +173,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from some source to `sink` for this configuration. */ - predicate hasFlowTo(Node sink) { - sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode() - } + predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) } /** * Holds if data may flow from some source to `sink` for this configuration. @@ -179,10 +181,12 @@ abstract class Configuration extends string { predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } /** + * DEPRECATED: Use `FlowExploration` instead. + * * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` * measured in approximate number of interprocedural steps. */ - int explorationLimit() { none() } + deprecated int explorationLimit() { none() } /** * Holds if hidden nodes should be included in the data flow graph. @@ -191,49 +195,6 @@ abstract class Configuration extends string { * is not visualized (for example in a `path-problem` query). */ predicate includeHiddenNodes() { none() } - - /** - * Holds if there is a partial data flow path from `source` to `node`. The - * approximate distance between `node` and the closest source is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards sink definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sources is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - */ - final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { - partialFlow(source, node, this) and - dist = node.getSourceDistance() - } - - /** - * Holds if there is a partial data flow path from `node` to `sink`. The - * approximate distance between `node` and the closest sink is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards source definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sinks is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - * - * Note that reverse flow has slightly lower precision than the corresponding - * forward flow, as reverse flow disregards type pruning among other features. - */ - final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { - revPartialFlow(node, sink, this) and - dist = node.getSinkDistance() - } } /** @@ -263,90 +224,6 @@ abstract private class ConfigurationRecursionPrevention extends Configuration { } } -private newtype TNodeEx = - TNodeNormal(Node n) or - TNodeImplicitRead(Node n, boolean hasRead) { - any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true] - } - -private class NodeEx extends TNodeEx { - string toString() { - result = this.asNode().toString() - or - exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") - } - - Node asNode() { this = TNodeNormal(result) } - - predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } - - Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } - - pragma[nomagic] - private DataFlowCallable getEnclosingCallable0() { - nodeEnclosingCallable(this.projectToNode(), result) - } - - pragma[inline] - DataFlowCallable getEnclosingCallable() { - pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) - } - - pragma[nomagic] - private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } - - pragma[inline] - DataFlowType getDataFlowType() { - pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) - } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -private class ArgNodeEx extends NodeEx { - ArgNodeEx() { this.asNode() instanceof ArgNode } -} - -private class ParamNodeEx extends NodeEx { - ParamNodeEx() { this.asNode() instanceof ParamNode } - - predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { - this.asNode().(ParamNode).isParameterOf(c, pos) - } - - ParameterPosition getPosition() { this.isParameterOf(_, result) } -} - -private class RetNodeEx extends NodeEx { - RetNodeEx() { this.asNode() instanceof ReturnNodeExt } - - ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } - - ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } -} - -private predicate inBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierIn(n) - | - config.isSource(n) or config.isSource(n, _) - ) -} - -private predicate outBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierOut(n) - | - config.isSink(n) or config.isSink(n, _) - ) -} - /** A bridge class to access the deprecated `isBarrierGuard`. */ private class BarrierGuardGuardedNodeBridge extends Unit { abstract predicate guardedNode(Node n, Configuration config); @@ -370,3019 +247,94 @@ private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { } } -pragma[nomagic] -private predicate fullBarrier(NodeEx node, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n) +private FlowState relevantState(Configuration config) { + config.isSource(_, result) or + config.isSink(_, result) or + config.isBarrier(_, result) or + config.isAdditionalFlowStep(_, result, _, _) or + config.isAdditionalFlowStep(_, _, _, result) +} + +private newtype TConfigState = + TMkConfigState(Configuration config, FlowState state) { + state = relevantState(config) or state instanceof FlowStateEmpty + } + +private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) } + +private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) } + +private predicate singleConfiguration() { 1 = strictcount(Configuration c) } + +private module Config implements FullStateConfigSig { + class FlowState = TConfigState; + + predicate isSource(Node source, FlowState state) { + getConfig(state).isSource(source, getState(state)) or - config.isBarrierIn(n) and - not config.isSource(n) and - not config.isSource(n, _) + getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty + } + + predicate isSink(Node sink, FlowState state) { + getConfig(state).isSink(sink, getState(state)) or - config.isBarrierOut(n) and - not config.isSink(n) and - not config.isSink(n, _) + getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty + } + + predicate isBarrier(Node node) { none() } + + predicate isBarrier(Node node, FlowState state) { + getConfig(state).isBarrier(node, getState(state)) or + getConfig(state).isBarrier(node) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state)) + } + + predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) } + + predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) } + + predicate isAdditionalFlowStep(Node node1, Node node2) { + singleConfiguration() and + any(Configuration config).isAdditionalFlowStep(node1, node2) + } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and + getConfig(state2) = getConfig(state1) or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config) - ) -} - -pragma[nomagic] -private predicate stateBarrier(NodeEx node, FlowState state, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n, state) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config) - ) -} - -pragma[nomagic] -private predicate sourceNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSource(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSource(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -pragma[nomagic] -private predicate sinkNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSink(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSink(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -/** Provides the relevant barriers for a step from `node1` to `node2`. */ -pragma[inline] -private predicate stepFilter(NodeEx node1, NodeEx node2, Configuration config) { - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) -} - -/** - * Holds if data can flow in one local step from `node1` to `node2`. - */ -private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.asNode() = n and - node2.isImplicitReadNode(n, false) and - not fullBarrier(node1, config) - ) -} - -/** - * Holds if the additional step from `node1` to `node2` does not jump between callables. - */ -private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.isImplicitReadNode(n, true) and - node2.asNode() = n and - not fullBarrier(node2, config) - ) -} - -private predicate additionalLocalStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) - ) -} - -/** - * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. - */ -private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if the additional step from `node1` to `node2` jumps between callables. - */ -private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate additionalJumpStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -pragma[nomagic] -private predicate readSet(NodeEx node1, ContentSet c, NodeEx node2, Configuration config) { - readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and - stepFilter(node1, node2, config) - or - exists(Node n | - node2.isImplicitReadNode(n, true) and - node1.isImplicitReadNode(n, _) and - config.allowImplicitRead(n, c) - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(ContentSet cs | - readSet(node1, cs, node2, config) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate clearsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - clearsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate expectsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - expectsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -pragma[nomagic] -private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } - -pragma[nomagic] -private predicate hasReadStep(Content c, Configuration config) { read(_, c, _, config) } - -pragma[nomagic] -private predicate store( - NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config -) { - store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), - contentType) and - hasReadStep(tc.getContent(), config) and - stepFilter(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { - viableReturnPosOut(call, pos, out.asNode()) -} - -pragma[nomagic] -private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { - viableParamArg(call, p.asNode(), arg.asNode()) -} - -/** - * Holds if field flow should be used for the given configuration. - */ -private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } - -private predicate hasSourceCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSourceCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate hasSinkCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSinkCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if flow from `p` to a return node of kind `kind` is allowed. - * - * We don't expect a parameter to return stored in itself, unless - * explicitly allowed - */ -bindingset[p, kind] -private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { - exists(ParameterPosition pos | p.isParameterOf(_, pos) | - not kind.(ParamUpdateReturnKind).getPosition() = pos - or - allowParameterReturnInSelfCached(p.asNode()) - ) -} - -private module Stage1 implements StageSig { - class Ap extends int { - // workaround for bad functionality-induced joins (happens when using `Unit`) - pragma[nomagic] - Ap() { this in [0 .. 1] and this < 1 } - } - - private class Cc = boolean; - - /* Begin: Stage 1 logic. */ - /** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `cc` records whether the node is reached through an - * argument in a call. - */ - private predicate fwdFlow(NodeEx node, Cc cc, Configuration config) { - sourceNode(node, _, config) and - if hasSourceCallCtx(config) then cc = true else cc = false - or - exists(NodeEx mid | fwdFlow(mid, cc, config) | - localFlowStep(mid, node, config) or - additionalLocalFlowStep(mid, node, config) or - additionalLocalStateStep(mid, _, node, _, config) - ) - or - exists(NodeEx mid | fwdFlow(mid, _, config) and cc = false | - jumpStep(mid, node, config) or - additionalJumpStep(mid, node, config) or - additionalJumpStateStep(mid, _, node, _, config) - ) - or - // store - exists(NodeEx mid | - useFieldFlow(config) and - fwdFlow(mid, cc, config) and - store(mid, _, node, _, config) - ) - or - // read - exists(ContentSet c | - fwdFlowReadSet(c, node, cc, config) and - fwdFlowConsCandSet(c, _, config) - ) - or - // flow into a callable - fwdFlowIn(_, _, _, node, config) and - cc = true - or - // flow out of a callable - fwdFlowOut(_, node, false, config) and - cc = false - or - // flow through a callable - exists(DataFlowCall call | - fwdFlowOutFromArg(call, node, config) and - fwdFlowIsEntered(call, cc, config) - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowIn( - DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p, Configuration config - ) { - // call context cannot help reduce virtual dispatch - fwdFlow(arg, cc, config) and - viableParamArgEx(call, p, arg) and - not fullBarrier(p, config) and - ( - cc = false - or - cc = true and - not reducedViableImplInCallContext(call, _, _) - ) - or - // call context may help reduce virtual dispatch - exists(DataFlowCallable target | - fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target, config) and - target = viableImplInSomeFwdFlowCallContextExt(call, config) and - cc = true - ) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - fwdFlowIn(call, _, cc, _, config) - } - - pragma[nomagic] - private predicate fwdFlowInReducedViableImplInSomeCallContext( - DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target, Configuration config - ) { - fwdFlow(arg, true, config) and - viableParamArgEx(call, p, arg) and - reducedViableImplInCallContext(call, _, _) and - target = p.getEnclosingCallable() and - not fullBarrier(p, config) - } - - /** - * Gets a viable dispatch target of `call` in the context `ctx`. This is - * restricted to those `call`s for which a context might make a difference, - * and to `ctx`s that are reachable in `fwdFlow`. - */ - pragma[nomagic] - private DataFlowCallable viableImplInSomeFwdFlowCallContextExt( - DataFlowCall call, Configuration config - ) { - exists(DataFlowCall ctx | - fwdFlowIsEntered(ctx, _, config) and - result = viableImplInCallContextExt(call, ctx) - ) - } - - private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) } - - pragma[nomagic] - private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc, Configuration config) { - exists(NodeEx mid | - fwdFlow(mid, cc, config) and - readSet(mid, c, node, config) - ) - } - - /** - * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - fwdFlow(mid, _, config) and - store(mid, tc, node, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `cs` may be interpreted in a read as the target of some store - * into `c`, in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCandSet(ContentSet cs, Content c, Configuration config) { - fwdFlowConsCand(c, config) and - c = cs.getAReadContent() - } - - pragma[nomagic] - private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { - exists(RetNodeEx ret | - fwdFlow(ret, cc, config) and - ret.getReturnPosition() = pos - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) { - exists(ReturnPosition pos | - fwdFlowReturnPosition(pos, cc, config) and - viableReturnPosOutEx(call, pos, out) and - not fullBarrier(out, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) { - fwdFlowOut(call, out, true, config) - } - - private predicate stateStepFwd(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1 | - additionalLocalStateStep(node1, state1, _, state2, config) or - additionalJumpStateStep(node1, state1, _, state2, config) - | - fwdFlow(node1, config) - ) - } - - private predicate fwdFlowState(FlowState state, Configuration config) { - sourceNode(_, state, config) - or - exists(FlowState state0 | - fwdFlowState(state0, config) and - stateStepFwd(state0, state, config) - ) - } - - /** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink. - */ - pragma[nomagic] - private predicate revFlow(NodeEx node, boolean toReturn, Configuration config) { - revFlow0(node, toReturn, config) and - fwdFlow(node, config) - } - - pragma[nomagic] - private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) { - exists(FlowState state | - fwdFlow(node, pragma[only_bind_into](config)) and - sinkNode(node, state, config) and - fwdFlowState(state, pragma[only_bind_into](config)) and - if hasSinkCallCtx(config) then toReturn = true else toReturn = false - ) - or - exists(NodeEx mid | revFlow(mid, toReturn, config) | - localFlowStep(node, mid, config) or - additionalLocalFlowStep(node, mid, config) or - additionalLocalStateStep(node, _, mid, _, config) - ) - or - exists(NodeEx mid | revFlow(mid, _, config) and toReturn = false | - jumpStep(node, mid, config) or - additionalJumpStep(node, mid, config) or - additionalJumpStateStep(node, _, mid, _, config) - ) - or - // store - exists(Content c | - revFlowStore(c, node, toReturn, config) and - revFlowConsCand(c, config) - ) - or - // read - exists(NodeEx mid, ContentSet c | - readSet(node, c, mid, config) and - fwdFlowConsCandSet(c, _, pragma[only_bind_into](config)) and - revFlow(mid, toReturn, pragma[only_bind_into](config)) - ) - or - // flow into a callable - revFlowIn(_, node, false, config) and - toReturn = false - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(pos, config) and - node.(RetNodeEx).getReturnPosition() = pos and - toReturn = true - ) - or - // flow through a callable - exists(DataFlowCall call | - revFlowInToReturn(call, node, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - /** - * Holds if `c` is the target of a read in the flow covered by `revFlow`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, ContentSet cs | - fwdFlow(node, pragma[only_bind_into](config)) and - readSet(node, cs, mid, config) and - fwdFlowConsCandSet(cs, c, pragma[only_bind_into](config)) and - revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) { - exists(NodeEx mid, TypedContent tc | - revFlow(mid, toReturn, pragma[only_bind_into](config)) and - fwdFlowConsCand(c, pragma[only_bind_into](config)) and - store(node, tc, mid, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `c` is the target of both a read and a store in the flow covered - * by `revFlow`. - */ - pragma[nomagic] - additional predicate revFlowIsReadAndStored(Content c, Configuration conf) { - revFlowConsCand(c, conf) and - revFlowStore(c, _, _, conf) - } - - pragma[nomagic] - additional predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config - ) { - fwdFlowReturnPosition(pos, _, config) and - viableReturnPosOutEx(call, pos, out) - } - - pragma[nomagic] - private predicate revFlowOut(ReturnPosition pos, Configuration config) { - exists(NodeEx out | - revFlow(out, _, config) and - viableReturnPosOutNodeCandFwd1(_, pos, out, config) - ) - } - - pragma[nomagic] - additional predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config - ) { - fwdFlowIn(call, arg, _, p, config) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate revFlowIn( - DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config - ) { - exists(ParamNodeEx p | - revFlow(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) { - revFlowIn(call, arg, true, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(NodeEx out | - revFlow(out, toReturn, config) and - fwdFlowOutFromArg(call, out, config) - ) - } - - private predicate stateStepRev(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - additionalLocalStateStep(node1, state1, node2, state2, config) or - additionalJumpStateStep(node1, state1, node2, state2, config) - | - revFlow(node1, _, pragma[only_bind_into](config)) and - revFlow(node2, _, pragma[only_bind_into](config)) and - fwdFlowState(state1, pragma[only_bind_into](config)) and - fwdFlowState(state2, pragma[only_bind_into](config)) - ) - } - - additional predicate revFlowState(FlowState state, Configuration config) { - exists(NodeEx node | - sinkNode(node, state, config) and - revFlow(node, _, pragma[only_bind_into](config)) and - fwdFlowState(state, pragma[only_bind_into](config)) - ) - or - exists(FlowState state0 | - revFlowState(state0, config) and - stateStepRev(state, state0, config) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Content c | - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - revFlow(node2, pragma[only_bind_into](config)) and - store(node1, tc, node2, contentType, config) and - c = tc.getContent() and - exists(ap1) - ) - } - - pragma[nomagic] - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) { - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - read(n1, c, n2, pragma[only_bind_into](config)) and - revFlow(n2, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, config) and - exists(ap) - } - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, _, pragma[only_bind_into](config)) and - exists(state) and - exists(ap) - } - - private predicate throughFlowNodeCand(NodeEx node, Configuration config) { - revFlow(node, true, config) and - fwdFlow(node, true, config) and - not inBarrier(node, config) and - not outBarrier(node, config) - } - - /** Holds if flow may return from `callable`. */ - pragma[nomagic] - private predicate returnFlowCallableNodeCand( - DataFlowCallable callable, ReturnKindExt kind, Configuration config - ) { - exists(RetNodeEx ret | - throughFlowNodeCand(ret, config) and - callable = ret.getEnclosingCallable() and - kind = ret.getKind() - ) - } - - /** - * Holds if flow may enter through `p` and reach a return node making `p` a - * candidate for the origin of a summary. - */ - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(DataFlowCallable c, ReturnKindExt kind | - throughFlowNodeCand(p, config) and - returnFlowCallableNodeCand(c, kind, config) and - p.getEnclosingCallable() = c and - exists(ap) and - parameterFlowThroughAllowed(p, kind) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - throughFlowNodeCand(ret, config) and - kind = ret.getKind() and - exists(argAp) and - exists(ap) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, boolean toReturn | - revFlow(arg, toReturn, config) and - revFlowInToReturn(call, arg, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, config)) and - fields = count(Content f0 | fwdFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | fwdFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, config)) and - fields = count(Content f0 | revFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | revFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | revFlow(n, b, config)) - } - /* End: Stage 1 logic. */ -} - -pragma[noinline] -private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - localFlowStep(node1, node2, config) -} - -pragma[noinline] -private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - additionalLocalFlowStep(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCand1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config -) { - Stage1::revFlow(out, config) and - Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, Configuration config -) { - exists(ReturnPosition pos | - viableReturnPosOutNodeCand1(call, pos, out, config) and - pos = ret.getReturnPosition() and - kind = pos.getKind() and - Stage1::revFlow(ret, config) and - not outBarrier(ret, config) and - not inBarrier(out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCand1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config -) { - Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and - Stage1::revFlow(arg, config) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config -) { - viableParamArgNodeCand1(call, p, arg, config) and - Stage1::revFlow(p, config) and - not outBarrier(arg, config) and - not inBarrier(p, config) -} - -/** - * Gets the amount of forward branching on the origin of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int branch(NodeEx n1, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n1, _, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) - ) -} - -/** - * Gets the amount of backward branching on the target of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int join(NodeEx n2, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n, _, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) - ) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. The - * `allowsFieldFlow` flag indicates whether the branching is within the limit - * specified by the configuration. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, ret, kind, out, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(ret, pragma[only_bind_into](config)) and - j = join(out, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. The `allowsFieldFlow` flag indicates whether - * the branching is within the limit specified by the configuration. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config -) { - flowIntoCallNodeCand1(call, arg, p, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(arg, pragma[only_bind_into](config)) and - j = join(p, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -private signature module StageSig { - class Ap; - - predicate revFlow(NodeEx node, Configuration config); - - predicate revFlowAp(NodeEx node, Ap ap, Configuration config); - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config); - - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config); - - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config); - - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ); - - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ); - - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config); -} - -private module MkStage { - class ApApprox = PrevStage::Ap; - - signature module StageParam { - class Ap; - - class ApNil extends Ap; - - bindingset[result, ap] - ApApprox getApprox(Ap ap); - - ApNil getApNil(NodeEx node); - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail); - - /** - * An approximation of `Content` that corresponds to the precision level of - * `Ap`, such that the mappings from both `Ap` and `Content` to this type - * are functional. - */ - class ApHeadContent; - - ApHeadContent getHeadContent(Ap ap); - - ApHeadContent projectToHeadContent(Content c); - - class ApOption; - - ApOption apNone(); - - ApOption apSome(Ap ap); - - class Cc; - - class CcCall extends Cc; - - // TODO: member predicate on CcCall - predicate matchesCall(CcCall cc, DataFlowCall call); - - class CcNoCall extends Cc; - - Cc ccNone(); - - CcCall ccSomeCall(); - - class LocalCc; - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc); - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ); - - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config - ); - - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config - ); - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config); - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType); - } - - module Stage implements StageSig { - import Param - - /* Begin: Stage logic. */ - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - private predicate revFlowApAlias(NodeEx node, ApApprox apa, Configuration config) { - PrevStage::revFlowAp(node, apa, config) - } - - pragma[nomagic] - private predicate flowIntoCallApa( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa, - Configuration config - ) { - flowIntoCall(call, arg, p, allowsFieldFlow, config) and - PrevStage::revFlowAp(p, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(arg, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowOutOfCallApa( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - ApApprox apa, Configuration config - ) { - flowOutOfCall(call, ret, kind, out, allowsFieldFlow, config) and - PrevStage::revFlowAp(out, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(ret, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowThroughOutOfCall( - DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, - ApApprox argApa, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa, pragma[only_bind_into](config)) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) and - matchesCall(ccc, call) - ) - } - - /** - * Holds if `node` is reachable with access path `ap` from a source in the - * configuration `config`. - * - * The call context `cc` records whether the node is reached through an - * argument in a call, and if so, `summaryCtx` and `argAp` record the - * corresponding parameter position and access path of that argument, respectively. - */ - pragma[nomagic] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa, config) and - PrevStage::revFlow(node, state, apa, config) and - filter(node, state, ap, config) - } - - pragma[inline] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, cc, summaryCtx, argAp, ap, _, config) - } - - pragma[nomagic] - private predicate fwdFlow0( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - sourceNode(node, state, config) and - (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and - argAp = apNone() and - summaryCtx = TParamNodeNone() and - ap = getApNil(node) and - apa = getApprox(ap) - or - exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | - fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0, config) and - localCc = getLocalCc(mid, cc) - | - localStep(mid, state0, node, state, true, _, config, localCc) and - ap = ap0 and - apa = apa0 - or - localStep(mid, state0, node, state, false, ap, config, localCc) and - ap0 instanceof ApNil and - apa = getApprox(ap) - ) - or - exists(NodeEx mid | - fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa, pragma[only_bind_into](config)) and - jumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(mid, state, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(mid, state0, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStateStep(mid, state0, node, state, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - // store - exists(TypedContent tc, Ap ap0 | - fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp, config) and - ap = apCons(tc, ap0) and - apa = getApprox(ap) - ) - or - // read - exists(Ap ap0, Content c | - fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp, config) and - fwdFlowConsCand(ap0, c, ap, config) and - apa = getApprox(ap) - ) - or - // flow into a callable - fwdFlowIn(_, node, state, _, cc, _, _, ap, apa, config) and - if PrevStage::parameterMayFlowThrough(node, apa, config) - then ( - summaryCtx = TParamNodeSome(node.asNode()) and - argAp = apSome(ap) - ) else ( - summaryCtx = TParamNodeNone() and argAp = apNone() - ) - or - // flow out of a callable - exists( - DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, - DataFlowCallable inner - | - fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa, config) and - flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa, config) and - inner = ret.getEnclosingCallable() and - cc = getCallContextReturn(inner, call, innercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - or - // flow through a callable - exists( - DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, ApApprox innerArgApa - | - fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowStore( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(DataFlowType contentType, ApApprox apa1 | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1, config) and - PrevStage::storeStepCand(node1, apa1, tc, node2, contentType, config) and - typecheckStore(ap1, contentType) - ) - } - - /** - * Holds if forward flow with access path `tail` reaches a store of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(TypedContent tc | - fwdFlowStore(_, tail, tc, _, _, _, _, _, config) and - tc.getContent() = c and - cons = apCons(tc, tail) - ) - } - - pragma[nomagic] - private predicate readStepCand( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - PrevStage::readStepCand(node1, c, node2, config) and - apc = projectToHeadContent(c) - } - - bindingset[node1, apc] - pragma[inline_late] - private predicate readStepCand0( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - readStepCand(node1, apc, c, node2, config) - } - - pragma[nomagic] - private predicate fwdFlowRead( - Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(ApHeadContent apc | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap, config) and - apc = getHeadContent(ap) and - readStepCand0(node1, apc, c, node2, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowIn( - DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, - ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa, Configuration config - ) { - exists(ArgNodeEx arg, boolean allowsFieldFlow | - fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa, config) and - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowRetFromArg( - RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, ApApprox argApa, - Ap ap, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - fwdFlow(pragma[only_bind_into](ret), state, ccc, - TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), - pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa), - pragma[only_bind_into](config)) and - kind = ret.getKind() and - parameterFlowThroughAllowed(summaryCtx, kind) and - argApa = getApprox(argAp) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) - ) - } - - pragma[inline] - private predicate fwdFlowThrough0( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, - Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa, config) and - fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp, config) - } - - pragma[nomagic] - private predicate fwdFlowThrough( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa, - config) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` - * and data might flow through the target callable and back out at `call`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered( - DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, - ParamNodeEx p, Ap ap, Configuration config - ) { - exists(ApApprox apa | - fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(p, apa, config) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate storeStepFwd( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config - ) { - fwdFlowStore(node1, ap1, tc, node2, _, _, _, _, config) and - ap2 = apCons(tc, ap1) and - fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _, config) - } - - private predicate readStepFwd( - NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config - ) { - fwdFlowRead(ap1, c, n1, n2, _, _, _, _, config) and - fwdFlowConsCand(ap1, c, ap2, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough0( - DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, - ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, - innerArgApa, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough( - RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, - Ap ap, Configuration config - ) { - exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | - returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa, config) and - pos = ret.getReturnPosition() and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowThroughIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap, - Configuration config - ) { - exists(ApApprox argApa | - flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), - allowsFieldFlow, argApa, pragma[only_bind_into](config)) and - fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa, - pragma[only_bind_into](config)) and - returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap, - pragma[only_bind_into](config)) and - if allowsFieldFlow = false then argAp instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowIntoCallAp( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap, - Configuration config - ) { - exists(ApApprox apa | - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - fwdFlow(arg, _, _, _, _, ap, apa, config) - ) - } - - pragma[nomagic] - private predicate flowOutOfCallAp( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, - Ap ap, Configuration config - ) { - exists(ApApprox apa | - flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa, config) and - fwdFlow(ret, _, _, _, _, ap, apa, config) and - pos = ret.getReturnPosition() - ) - } - - /** - * Holds if `node` with access path `ap` is part of a path from a source to a - * sink in the configuration `config`. - * - * The parameter `returnCtx` records whether (and how) the node must be returned - * from the enclosing callable in order to reach a sink, and if so, `returnAp` - * records the access path of the returned value. - */ - pragma[nomagic] - additional predicate revFlow( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - revFlow0(node, state, returnCtx, returnAp, ap, config) and - fwdFlow(node, state, _, _, _, ap, config) - } - - pragma[nomagic] - private predicate revFlow0( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, _, _, _, ap, config) and - sinkNode(node, state, config) and - ( - if hasSinkCallCtx(config) - then returnCtx = TReturnCtxNoFlowThrough() - else returnCtx = TReturnCtxNone() - ) and - returnAp = apNone() and - ap instanceof ApNil - or - exists(NodeEx mid, FlowState state0 | - localStep(node, state, mid, state0, true, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, ap, config) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap, pragma[only_bind_into](config)) and - localStep(node, pragma[only_bind_into](state), mid, state0, false, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, nil, pragma[only_bind_into](config)) and - ap instanceof ApNil - ) - or - exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, state, _, _, ap, config) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStep(node, mid, config) and - revFlow(pragma[only_bind_into](mid), state, _, _, nil, pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStateStep(node, state, mid, state0, config) and - revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil, - pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - // store - exists(Ap ap0, Content c | - revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp, config) and - revFlowConsCand(ap0, c, ap, config) - ) - or - // read - exists(NodeEx mid, Ap ap0 | - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - readStepFwd(node, ap, _, mid, ap0, config) - ) - or - // flow into a callable - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, state, TReturnCtxNone(), returnAp, ap, config) and - flowIntoCallAp(_, node, p, allowsFieldFlow, ap, config) and - (if allowsFieldFlow = false then ap instanceof ApNil else any()) and - returnCtx = TReturnCtxNone() - ) - or - // flow through a callable - exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, node, p, _, ap, innerReturnAp, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(_, node, pos, state, _, _, ap, config) and - if returnFlowsThrough(node, pos, state, _, _, _, ap, config) - then ( - returnCtx = TReturnCtxMaybeFlowThrough(pos) and - returnAp = apSome(ap) - ) else ( - returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() - ) - ) - } - - pragma[nomagic] - private predicate revFlowStore( - Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, - ReturnCtx returnCtx, ApOption returnAp, Configuration config - ) { - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - storeStepFwd(node, ap, tc, mid, ap0, config) and - tc.getContent() = c - } - - /** - * Holds if reverse flow with access path `tail` reaches a read of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(NodeEx mid, Ap tail0 | - revFlow(mid, _, _, _, tail, config) and - tail = pragma[only_bind_into](tail0) and - readStepFwd(_, cons, c, mid, tail0, config) - ) - } - - pragma[nomagic] - private predicate revFlowOut( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, - ApOption returnAp, Ap ap, Configuration config - ) { - exists(NodeEx out, boolean allowsFieldFlow | - revFlow(out, state, returnCtx, returnAp, ap, config) and - flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate revFlowParamToReturn( - ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap, Configuration config - ) { - revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), - pragma[only_bind_into](ap), pragma[only_bind_into](config)) and - parameterFlowThroughAllowed(p, pos.getKind()) and - PrevStage::parameterMayFlowThrough(p, getApprox(ap), config) - } - - pragma[nomagic] - private predicate revFlowThrough( - DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, - ApOption returnAp, Ap ap, Ap innerReturnAp, Configuration config - ) { - revFlowParamToReturn(p, state, pos, innerReturnAp, ap, config) and - revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned( - DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap, - Configuration config - ) { - exists(RetNodeEx ret, FlowState state, CcCall ccc | - revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap, config) and - returnFlowsThrough(ret, pos, state, ccc, _, _, ap, config) and - matchesCall(ccc, call) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Ap ap2, Content c | - PrevStage::storeStepCand(node1, _, tc, node2, contentType, config) and - revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _, config) and - revFlowConsCand(ap2, c, ap1, config) - ) - } - - predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(Ap ap1, Ap ap2 | - revFlow(node2, _, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and - readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _, - pragma[only_bind_into](config)) - ) - } - - additional predicate revFlow(NodeEx node, FlowState state, Configuration config) { - revFlow(node, state, _, _, _, config) - } - - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, _, _, ap, config) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, _, _, _, ap, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, Configuration config) { - revFlow(node, _, _, _, _, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, ap, config) - } - - private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepFwd(_, ap, tc, _, _, config) - } - - private predicate revConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepCand(_, ap, tc, _, _, config) - } - - private predicate validAp(Ap ap, Configuration config) { - revFlow(_, _, _, _, ap, config) and ap instanceof ApNil - or - exists(TypedContent head, Ap tail | - consCand(head, tail, config) and - ap = apCons(head, tail) - ) - } - - additional predicate consCand(TypedContent tc, Ap ap, Configuration config) { - revConsCand(tc, ap, config) and - validAp(ap, config) - } - - pragma[nomagic] - private predicate parameterFlowsThroughRev( - ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp, Configuration config - ) { - revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap, config) and - parameterFlowThroughAllowed(p, pos.getKind()) - } - - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(ReturnPosition pos | - returnFlowsThrough(_, pos, _, _, p, ap, _, config) and - parameterFlowsThroughRev(p, ap, pos, _, config) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - exists(ParamNodeEx p, ReturnPosition pos | - returnFlowsThrough(ret, pos, _, _, p, argAp, ap, config) and - parameterFlowsThroughRev(p, argAp, pos, ap, config) and - kind = pos.getKind() - ) - } - - pragma[nomagic] - private predicate revFlowThroughArg( - DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, - Ap ap, Configuration config - ) { - exists(ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp, config) - ) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | - revFlow(arg, state, returnCtx, returnAp, ap, config) and - revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, config)) and - fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and - states = count(FlowState state | fwdFlow(_, state, _, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | - fwdFlow(n, state, cc, summaryCtx, argAp, ap, config) - ) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, _, _, _, config)) and - fields = count(TypedContent f0 | consCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and - states = count(FlowState state | revFlow(_, state, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | - revFlow(n, state, returnCtx, retAp, ap, config) - ) - } - /* End: Stage logic. */ - } -} - -private module BooleanCallContext { - class Cc extends boolean { - Cc() { this in [true, false] } - } - - class CcCall extends Cc { - CcCall() { this = true } - } - - /** Holds if the call context may be `call`. */ - predicate matchesCall(CcCall cc, DataFlowCall call) { any() } - - class CcNoCall extends Cc { - CcNoCall() { this = false } - } - - Cc ccNone() { result = false } - - CcCall ccSomeCall() { result = true } - - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } -} - -private module Level1CallContext { - class Cc = CallContext; - - class CcCall = CallContextCall; - - pragma[inline] - predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } - - class CcNoCall = CallContextNoCall; - - Cc ccNone() { result instanceof CallContextAny } - - CcCall ccSomeCall() { result instanceof CallContextSomeCall } - - module NoLocalCallContext { - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSiteDispatch(call, c) - then result = TSpecificCall(call) - else result = TSomeCall() - } - } - - module LocalCallContext { - class LocalCc = LocalCallContext; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { - result = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - node.getEnclosingCallable()) - } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() - } - } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { - checkCallContextReturn(innercc, c, call) and - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() - } -} - -private module Stage2Param implements MkStage::StageParam { - private module PrevStage = Stage1; - - class Ap extends boolean { - Ap() { this in [true, false] } - } - - class ApNil extends Ap { - ApNil() { this = false } - } - - bindingset[result, ap] - PrevStage::Ap getApprox(Ap ap) { any() } - - ApNil getApNil(NodeEx node) { Stage1::revFlow(node, _) and exists(result) } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } - - class ApHeadContent = Unit; - - pragma[inline] - ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } - - ApHeadContent projectToHeadContent(Content c) { any() } - - class ApOption = BooleanOption; - - ApOption apNone() { result = TBooleanNone() } - - ApOption apSome(Ap ap) { result = TBooleanSome(ap) } - - import Level1CallContext - import NoLocalCallContext - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - ( - preservesValue = true and - localFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalStateStep(node1, state1, node2, state2, config) - ) and - exists(ap) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand1/6; - - predicate flowIntoCall = flowIntoCallNodeCand1/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - expectsContentEx(node, c) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - PrevStage::revFlowState(state, pragma[only_bind_into](config)) and - exists(ap) and - not stateBarrier(node, state, config) and - ( - notExpectsContent(node) - or - ap = true and - expectsContentCand(node, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage2 implements StageSig { - import MkStage::Stage -} - -pragma[nomagic] -private predicate flowOutOfCallNodeCand2( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -pragma[nomagic] -private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -private module LocalFlowBigStep { - /** - * A node where some checking is required, and hence the big-step relation - * is not allowed to step over. - */ - private class FlowCheckNode extends NodeEx { - FlowCheckNode() { - castNode(this.asNode()) or - clearsContentCached(this.asNode(), _) or - expectsContentCached(this.asNode(), _) - } - } - - /** - * Holds if `node` can be the first node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowEntry(NodeEx node, FlowState state, Configuration config) { - Stage2::revFlow(node, state, config) and - ( - sourceNode(node, state, config) - or - jumpStep(_, node, config) - or - additionalJumpStep(_, node, config) - or - additionalJumpStateStep(_, _, node, state, config) - or - node instanceof ParamNodeEx - or - node.asNode() instanceof OutNodeExt - or - Stage2::storeStepCand(_, _, _, node, _, config) - or - Stage2::readStepCand(_, _, node, config) - or - node instanceof FlowCheckNode - or - exists(FlowState s | - additionalLocalStateStep(_, s, node, state, config) and - s != state - ) - ) - } - - /** - * Holds if `node` can be the last node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowExit(NodeEx node, FlowState state, Configuration config) { - exists(NodeEx next | Stage2::revFlow(next, state, config) | - jumpStep(node, next, config) or - additionalJumpStep(node, next, config) or - flowIntoCallNodeCand2(_, node, next, _, config) or - flowOutOfCallNodeCand2(_, node, _, next, _, config) or - Stage2::storeStepCand(node, _, _, next, _, config) or - Stage2::readStepCand(node, _, next, config) - ) - or - exists(NodeEx next, FlowState s | Stage2::revFlow(next, s, config) | - additionalJumpStateStep(node, state, next, s, config) - or - additionalLocalStateStep(node, state, next, s, config) and - s != state - ) - or - Stage2::revFlow(node, state, config) and - node instanceof FlowCheckNode - or - sinkNode(node, state, config) - } - - pragma[noinline] - private predicate additionalLocalFlowStepNodeCand2( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, Configuration config - ) { - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 and - Stage2::revFlow(node1, pragma[only_bind_into](state1), false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false, - pragma[only_bind_into](config)) - or - additionalLocalStateStep(node1, state1, node2, state2, config) and - Stage2::revFlow(node1, state1, false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, state2, false, pragma[only_bind_into](config)) - } - - /** - * Holds if the local path from `node1` to `node2` is a prefix of a maximal - * subsequence of local flow steps in a dataflow path. - * - * This is the transitive closure of `[additional]localFlowStep` beginning - * at `localFlowEntry`. - */ - pragma[nomagic] - private predicate localFlowStepPlus( - NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, - Configuration config, LocalCallContext cc - ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and - ( - localFlowEntry(node1, pragma[only_bind_into](state), pragma[only_bind_into](config)) and - ( - localFlowStepNodeCand1(node1, node2, config) and - preservesValue = true and - t = node1.getDataFlowType() and // irrelevant dummy value - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - or - additionalLocalFlowStepNodeCand2(node1, state, node2, state, config) and - preservesValue = false and - t = node2.getDataFlowType() - ) and - node1 != node2 and - cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) - or - exists(NodeEx mid | - localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, - pragma[only_bind_into](config), cc) and - localFlowStepNodeCand1(mid, node2, config) and - not mid instanceof FlowCheckNode and - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - ) - or - exists(NodeEx mid | - localFlowStepPlus(node1, state, mid, _, _, pragma[only_bind_into](config), cc) and - additionalLocalFlowStepNodeCand2(mid, state, node2, state, config) and - not mid instanceof FlowCheckNode and - preservesValue = false and - t = node2.getDataFlowType() - ) - ) - } - - /** - * Holds if `node1` can step to `node2` in one or more local steps and this - * path can occur as a maximal subsequence of local steps in a dataflow path. - */ - pragma[nomagic] - predicate localFlowBigStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - DataFlowType t, Configuration config, LocalCallContext callContext - ) { - localFlowStepPlus(node1, state1, node2, preservesValue, t, config, callContext) and - localFlowExit(node2, state1, config) and - state1 = state2 - or - additionalLocalFlowStepNodeCand2(node1, state1, node2, state2, config) and - state1 != state2 and - preservesValue = false and - t = node2.getDataFlowType() and - callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) - } -} - -private import LocalFlowBigStep - -private module Stage3Param implements MkStage::StageParam { - private module PrevStage = Stage2; - - class Ap = ApproxAccessPathFront; - - class ApNil = ApproxAccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } - - class ApHeadContent = ContentApprox; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - predicate projectToHeadContent = getContentApprox/1; - - class ApOption = ApproxAccessPathFrontOption; - - ApOption apNone() { result = TApproxAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } - - import BooleanCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApproxAccessPathFrontNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand2/6; - - predicate flowIntoCall = flowIntoCallNodeCand2/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getAHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage3 implements StageSig { - import MkStage::Stage -} - -private module Stage4Param implements MkStage::StageParam { - private module PrevStage = Stage3; - - class Ap = AccessPathFront; - - class ApNil = AccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathFrontOption; - - ApOption apNone() { result = TAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } - - import BooleanCallContext - - pragma[nomagic] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) and - exists(lcc) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clearSet(NodeEx node, ContentSet c, Configuration config) { - PrevStage::revFlow(node, config) and - clearsContentCached(node.asNode(), c) - } - - pragma[nomagic] - private predicate clearContent(NodeEx node, Content c, Configuration config) { - exists(ContentSet cs | - PrevStage::readStepCand(_, pragma[only_bind_into](c), _, pragma[only_bind_into](config)) and - c = cs.getAReadContent() and - clearSet(node, cs, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clear(NodeEx node, Ap ap, Configuration config) { - clearContent(node, ap.getHead().getContent(), config) - } - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - not clear(node, ap, config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage4 implements StageSig { - import MkStage::Stage -} - -/** - * Holds if `argApf` is recorded as the summary context for flow reaching `node` - * and remains relevant for the following pruning stage. - */ -private predicate flowCandSummaryCtx( - NodeEx node, FlowState state, AccessPathFront argApf, Configuration config -) { - exists(AccessPathFront apf | - Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf, config) and - Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf, - config) - ) -} - -/** - * Holds if a length 2 access path approximation with the head `tc` is expected - * to be expensive. - */ -private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { - exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf, config)) and - nodes = - strictcount(NodeEx n, FlowState state | - Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - or - flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - ) and - accessPathApproxCostLimits(apLimit, tupleLimit) and - apLimit < tails and - tupleLimit < (tails - 1) * nodes and - not tc.forceHighPrecision() - ) -} - -private newtype TAccessPathApprox = - TNil(DataFlowType t) or - TConsNil(TypedContent tc, DataFlowType t) { - Stage4::consCand(tc, TFrontNil(t), _) and - not expensiveLen2unfolding(tc, _) - } or - TConsCons(TypedContent tc1, TypedContent tc2, int len) { - Stage4::consCand(tc1, TFrontHead(tc2), _) and - len in [2 .. accessPathLimit()] and - not expensiveLen2unfolding(tc1, _) - } or - TCons1(TypedContent tc, int len) { - len in [1 .. accessPathLimit()] and - expensiveLen2unfolding(tc, _) - } - -/** - * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only - * the first two elements of the list and its length are tracked. If data flows - * from a source to a given node with a given `AccessPathApprox`, this indicates - * the sequence of dereference operations needed to get from the value in the node - * to the tracked object. The final type indicates the type of the tracked object. - */ -abstract private class AccessPathApprox extends TAccessPathApprox { - abstract string toString(); - - abstract TypedContent getHead(); - - abstract int len(); - - abstract DataFlowType getType(); - - abstract AccessPathFront getFront(); - - /** Gets the access path obtained by popping `head` from this path, if any. */ - abstract AccessPathApprox pop(TypedContent head); -} - -private class AccessPathApproxNil extends AccessPathApprox, TNil { - private DataFlowType t; - - AccessPathApproxNil() { this = TNil(t) } - - override string toString() { result = concat(": " + ppReprType(t)) } - - override TypedContent getHead() { none() } - - override int len() { result = 0 } - - override DataFlowType getType() { result = t } - - override AccessPathFront getFront() { result = TFrontNil(t) } - - override AccessPathApprox pop(TypedContent head) { none() } -} - -abstract private class AccessPathApproxCons extends AccessPathApprox { } - -private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { - private TypedContent tc; - private DataFlowType t; - - AccessPathApproxConsNil() { this = TConsNil(tc, t) } - - override string toString() { - // The `concat` becomes "" if `ppReprType` has no result. - result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) - } - - override TypedContent getHead() { result = tc } - - override int len() { result = 1 } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } -} - -private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { - private TypedContent tc1; - private TypedContent tc2; - private int len; - - AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - - override string toString() { - if len = 2 - then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" - else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc1 } - - override int len() { result = len } - - override DataFlowType getType() { result = tc1.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc1) } - - override AccessPathApprox pop(TypedContent head) { - head = tc1 and - ( - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - } -} - -private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { - private TypedContent tc; - private int len; - - AccessPathApproxCons1() { this = TCons1(tc, len) } - - override string toString() { - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc } - - override int len() { result = len } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { - head = tc and - ( - exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2), _) | - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - or - exists(DataFlowType t | - len = 1 and - Stage4::consCand(tc, TFrontNil(t), _) and - result = TNil(t) - ) - ) - } -} - -/** Gets the access path obtained by popping `tc` from `ap`, if any. */ -private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } - -/** Gets the access path obtained by pushing `tc` onto `ap`. */ -private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } - -private newtype TAccessPathApproxOption = - TAccessPathApproxNone() or - TAccessPathApproxSome(AccessPathApprox apa) - -private class AccessPathApproxOption extends TAccessPathApproxOption { - string toString() { - this = TAccessPathApproxNone() and result = "" - or - this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) - } -} - -private module Stage5Param implements MkStage::StageParam { - private module PrevStage = Stage4; - - class Ap = AccessPathApprox; - - class ApNil = AccessPathApproxNil; - - pragma[nomagic] - PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathApproxOption; - - ApOption apNone() { result = TAccessPathApproxNone() } - - ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } - - import Level1CallContext - import LocalCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, lcc) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { any() } - - // Type checking is not necessary here as it has already been done in stage 3. - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage5 = MkStage::Stage; - -bindingset[conf, result] -private Configuration unbindConf(Configuration conf) { - exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c)) -} - -pragma[nomagic] -private predicate nodeMayUseSummary0( - NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(AccessPathApprox apa0 | - Stage5::parameterMayFlowThrough(p, _, _) and - Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0, config) and - Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), - TAccessPathApproxSome(apa), apa0, config) - ) -} - -pragma[nomagic] -private predicate nodeMayUseSummary( - NodeEx n, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::parameterMayFlowThrough(p, apa, config) and - nodeMayUseSummary0(n, p, state, apa, config) - ) -} - -private newtype TSummaryCtx = - TSummaryCtxNone() or - TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { - exists(Configuration config | - Stage5::parameterMayFlowThrough(p, ap.getApprox(), config) and - Stage5::revFlow(p, state, _, config) - ) - } - -/** - * A context for generating flow summaries. This represents flow entry through - * a specific parameter with an access path of a specific shape. - * - * Summaries are only created for parameters that may flow through. - */ -abstract private class SummaryCtx extends TSummaryCtx { - abstract string toString(); -} - -/** A summary context from which no flow summary can be generated. */ -private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { - override string toString() { result = "" } -} - -/** A summary context from which a flow summary can be generated. */ -private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParamNodeEx p; - private FlowState s; - private AccessPath ap; - - SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } - - ParameterPosition getParameterPos() { p.isParameterOf(_, result) } - - ParamNodeEx getParamNode() { result = p } - - override string toString() { result = p + ": " + ap } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** - * Gets the number of length 2 access path approximations that correspond to `apa`. - */ -private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { - exists(TypedContent tc, int len | - tc = apa.getHead() and - len = apa.len() and - result = - strictcount(AccessPathFront apf | - Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), - config) - ) - ) -} - -private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = - strictcount(NodeEx n, FlowState state | - Stage5::revFlow(n, state, apa, config) or nodeMayUseSummary(n, state, apa, config) - ) -} - -/** - * Holds if a length 2 access path approximation matching `apa` is expected - * to be expensive. - */ -private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = count1to2unfold(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - apLimit < aps and - tupleLimit < (aps - 1) * nodes - ) -} - -private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { - exists(TypedContent head | - apa.pop(head) = result and - Stage5::consCand(head, result, config) - ) -} - -/** - * Holds with `unfold = false` if a precise head-tail representation of `apa` is - * expected to be expensive. Holds with `unfold = true` otherwise. - */ -private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { - if apa.getHead().forceHighPrecision() - then unfold = true - else - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = countPotentialAps(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true - ) -} - -/** - * Gets the number of `AccessPath`s that correspond to `apa`. - */ -pragma[assume_small_delta] -private int countAps(AccessPathApprox apa, Configuration config) { - evalUnfold(apa, false, config) and - result = 1 and - (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) - or - evalUnfold(apa, false, config) and - result = count1to2unfold(apa, config) and - not expensiveLen1to2unfolding(apa, config) - or - evalUnfold(apa, true, config) and - result = countPotentialAps(apa, config) -} - -/** - * Gets the number of `AccessPath`s that would correspond to `apa` assuming - * that it is expanded to a precise head-tail representation. - */ -language[monotonicAggregates] -pragma[assume_small_delta] -private int countPotentialAps(AccessPathApprox apa, Configuration config) { - apa instanceof AccessPathApproxNil and result = 1 - or - result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) -} - -private newtype TAccessPath = - TAccessPathNil(DataFlowType t) or - TAccessPathCons(TypedContent head, AccessPath tail) { - exists(AccessPathApproxCons apa | - not evalUnfold(apa, false, _) and - head = apa.getHead() and - tail.getApprox() = getATail(apa, _) - ) - } or - TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - not expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head1 = apa.getHead() and - head2 = getATail(apa, _).getHead() - ) - } or - TAccessPathCons1(TypedContent head, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head = apa.getHead() - ) - } - -private newtype TPathNode = - pragma[assume_small_delta] - TPathNodeMid( - NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config - ) { - // A PathNode is introduced by a source ... - Stage5::revFlow(node, state, config) and - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - or - // ... or a step from an existing PathNode to another node. - exists(PathNodeMid mid | - pathStep(mid, node, state, cc, sc, ap) and - pragma[only_bind_into](config) = mid.getConfiguration() and - Stage5::revFlow(node, state, ap.getApprox(), pragma[only_bind_into](config)) - ) - } or - TPathNodeSink(NodeEx node, FlowState state, Configuration config) { - exists(PathNodeMid sink | - sink.isAtSink() and - node = sink.getNodeEx() and - state = sink.getState() and - config = sink.getConfiguration() - ) - } or - TPathNodeSourceGroup(string sourceGroup, Configuration config) { - exists(PathNodeImpl source | - sourceGroup = source.getSourceGroup() and - config = source.getConfiguration() - ) - } or - TPathNodeSinkGroup(string sinkGroup, Configuration config) { - exists(PathNodeSink sink | - sinkGroup = sink.getSinkGroup() and - config = sink.getConfiguration() - ) - } - -/** - * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a - * source to a given node with a given `AccessPath`, this indicates the sequence - * of dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ -private class AccessPath extends TAccessPath { - /** Gets the head of this access path, if any. */ - abstract TypedContent getHead(); - - /** Gets the tail of this access path, if any. */ - abstract AccessPath getTail(); - - /** Gets the front of this access path. */ - abstract AccessPathFront getFront(); - - /** Gets the approximation of this access path. */ - abstract AccessPathApprox getApprox(); - - /** Gets the length of this access path. */ - abstract int length(); - - /** Gets a textual representation of this access path. */ - abstract string toString(); - - /** Gets the access path obtained by popping `tc` from this access path, if any. */ - final AccessPath pop(TypedContent tc) { - result = this.getTail() and - tc = this.getHead() - } - - /** Gets the access path obtained by pushing `tc` onto this access path. */ - final AccessPath push(TypedContent tc) { this = result.pop(tc) } -} - -private class AccessPathNil extends AccessPath, TAccessPathNil { - private DataFlowType t; - - AccessPathNil() { this = TAccessPathNil(t) } - - DataFlowType getType() { result = t } - - override TypedContent getHead() { none() } - - override AccessPath getTail() { none() } - - override AccessPathFrontNil getFront() { result = TFrontNil(t) } - - override AccessPathApproxNil getApprox() { result = TNil(t) } - - override int length() { result = 0 } - - override string toString() { result = concat(": " + ppReprType(t)) } -} - -private class AccessPathCons extends AccessPath, TAccessPathCons { - private TypedContent head; - private AccessPath tail; - - AccessPathCons() { this = TAccessPathCons(head, tail) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { result = tail } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - pragma[assume_small_delta] - override AccessPathApproxCons getApprox() { - result = TConsNil(head, tail.(AccessPathNil).getType()) - or - result = TConsCons(head, tail.getHead(), this.length()) - or - result = TCons1(head, this.length()) - } - - pragma[assume_small_delta] - override int length() { result = 1 + tail.length() } - - private string toStringImpl(boolean needsSuffix) { - exists(DataFlowType t | - tail = TAccessPathNil(t) and - needsSuffix = false and - result = head.toString() + "]" + concat(" : " + ppReprType(t)) - ) - or - result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) - or - exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | - result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true - or - result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false - ) - or - exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | - result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true - or - result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false - ) - } - - override string toString() { - result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" - or - result = "[" + this.toStringImpl(false) - } -} - -private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { - private TypedContent head1; - private TypedContent head2; - private int len; - - AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } - - override TypedContent getHead() { result = head1 } - - override AccessPath getTail() { - Stage5::consCand(head1, result.getApprox(), _) and - result.getHead() = head2 and - result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head1) } - - override AccessPathApproxCons getApprox() { - result = TConsCons(head1, head2, len) or - result = TCons1(head1, len) - } - - override int length() { result = len } - - override string toString() { - if len = 2 - then result = "[" + head1.toString() + ", " + head2.toString() + "]" - else - result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" - } -} - -private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { - private TypedContent head; - private int len; - - AccessPathCons1() { this = TAccessPathCons1(head, len) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { - Stage5::consCand(head, result.getApprox(), _) and result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - - override int length() { result = len } - - override string toString() { - if len = 1 - then result = "[" + head.toString() + "]" - else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" - } -} - -abstract private class PathNodeImpl extends TPathNode { - /** Gets the `FlowState` of this node. */ - abstract FlowState getState(); - - /** Gets the associated configuration. */ - abstract Configuration getConfiguration(); - - /** Holds if this node is a source. */ - abstract predicate isSource(); - - abstract PathNodeImpl getASuccessorImpl(); - - private PathNodeImpl getASuccessorIfHidden() { - this.isHidden() and - result = this.getASuccessorImpl() - } - - pragma[nomagic] - private PathNodeImpl getANonHiddenSuccessor0() { - result = this.getASuccessorIfHidden*() and - not result.isHidden() - } - - final PathNodeImpl getANonHiddenSuccessor() { - result = this.getASuccessorImpl().getANonHiddenSuccessor0() and - not this.isHidden() - } - - abstract NodeEx getNodeEx(); - - predicate isHidden() { - not this.getConfiguration().includeHiddenNodes() and - ( - hiddenNode(this.getNodeEx().asNode()) and - not this.isSource() and - not this instanceof PathNodeSink - or - this.getNodeEx() instanceof TNodeImplicitRead - ) - } - - string getSourceGroup() { - this.isSource() and - this.getConfiguration().sourceGrouping(this.getNodeEx().asNode(), result) + not singleConfiguration() and + getConfig(state1).isAdditionalFlowStep(node1, node2) and + state2 = state1 } - predicate isFlowSource() { - this.isSource() and not exists(this.getSourceGroup()) - or - this instanceof PathNodeSourceGroup + predicate allowImplicitRead(Node node, ContentSet c) { + any(Configuration config).allowImplicitRead(node, c) } - predicate isFlowSink() { - this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or - this instanceof PathNodeSinkGroup - } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } - private string ppAp() { - this instanceof PathNodeSink and result = "" - or - exists(string s | s = this.(PathNodeMid).getAp().toString() | - if s = "" then result = "" else result = " " + s - ) - } + FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } - private string ppCtx() { - this instanceof PathNodeSink and result = "" - or - result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + predicate sourceGrouping(Node source, string sourceGroup) { + any(Configuration config).sourceGrouping(source, sourceGroup) } - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + predicate sinkGrouping(Node sink, string sinkGroup) { + any(Configuration config).sinkGrouping(sink, sinkGroup) } -} - -/** Holds if `n` can reach a sink. */ -private predicate directReach(PathNodeImpl n) { - n instanceof PathNodeSink or - n instanceof PathNodeSinkGroup or - directReach(n.getANonHiddenSuccessor()) -} - -/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ -private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } -/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { - n1.getANonHiddenSuccessor() = n2 and directReach(n2) + predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() } } -private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) +private import Impl as I +import I /** * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. */ -class PathNode instanceof PathNodeImpl { - PathNode() { reach(this) } - +class PathNode instanceof I::PathNode { /** Gets a textual representation of this element. */ final string toString() { result = super.toString() } @@ -3406,1548 +358,39 @@ class PathNode instanceof PathNodeImpl { } /** Gets the underlying `Node`. */ - final Node getNode() { super.getNodeEx().projectToNode() = result } + final Node getNode() { result = super.getNode() } /** Gets the `FlowState` of this node. */ - final FlowState getState() { result = super.getState() } + final FlowState getState() { result = getState(super.getState()) } /** Gets the associated configuration. */ - final Configuration getConfiguration() { result = super.getConfiguration() } + final Configuration getConfiguration() { result = getConfig(super.getState()) } /** Gets a successor of this node, if any. */ - final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } + final PathNode getASuccessor() { result = super.getASuccessor() } /** Holds if this node is a source. */ final predicate isSource() { super.isSource() } /** Holds if this node is a grouping of source nodes. */ - final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group, _) } + final predicate isSourceGroup(string group) { super.isSourceGroup(group) } /** Holds if this node is a grouping of sink nodes. */ - final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group, _) } + final predicate isSinkGroup(string group) { super.isSinkGroup(group) } } -/** - * Provides the query predicates needed to include a graph in a path-problem query. - */ -module PathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } - - /** Holds if `n` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode n, string key, string val) { - key = "semmle.label" and val = n.toString() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { - Subpaths::subpaths(arg, par, ret, out) - } -} - -/** - * An intermediate flow graph node. This is a triple consisting of a `Node`, - * a `CallContext`, and a `Configuration`. - */ -private class PathNodeMid extends PathNodeImpl, TPathNodeMid { - NodeEx node; - FlowState state; - CallContext cc; - SummaryCtx sc; - AccessPath ap; - Configuration config; - - PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - SummaryCtx getSummaryCtx() { result = sc } - - AccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - private PathNodeMid getSuccMid() { - pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx(), result.getAp()) and - result.getConfiguration() = unbindConf(this.getConfiguration()) - } - - override PathNodeImpl getASuccessorImpl() { - // an intermediate step to another intermediate node - result = this.getSuccMid() - or - // a final step to a sink - result = this.getSuccMid().projectToSink() - } - - override predicate isSource() { - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - } - - predicate isAtSink() { - sinkNode(node, state, config) and - ap instanceof AccessPathNil and - if hasSinkCallCtx(config) - then - // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` - // is exactly what we need to check. This also implies - // `sc instanceof SummaryCtxNone`. - // For `FeatureEqualSourceSinkCallContext` the initial call context was - // set to `CallContextSomeCall` and jumps are disallowed, so - // `cc instanceof CallContextNoCall` never holds. On the other hand, - // in this case there's never any need to enter a call except to identify - // a summary, so the condition in `pathIntoCallable` enforces this, which - // means that `sc instanceof SummaryCtxNone` holds if and only if we are - // in the call context of the source. - sc instanceof SummaryCtxNone or - cc instanceof CallContextNoCall - else any() - } - - PathNodeSink projectToSink() { - this.isAtSink() and - result.getNodeEx() = node and - result.getState() = state and - result.getConfiguration() = unbindConf(config) - } -} - -/** - * A flow graph node corresponding to a sink. This is disjoint from the - * intermediate nodes in order to uniquely correspond to a given sink by - * excluding the `CallContext`. - */ -private class PathNodeSink extends PathNodeImpl, TPathNodeSink { - NodeEx node; - FlowState state; - Configuration config; - - PathNodeSink() { this = TPathNodeSink(node, state, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result = TPathNodeSinkGroup(this.getSinkGroup(), config) - } - - override predicate isSource() { sourceNode(node, state, config) } - - string getSinkGroup() { config.sinkGrouping(node.asNode(), result) } -} - -private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { - string sourceGroup; - Configuration config; - - PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result.getSourceGroup() = sourceGroup and - result.getConfiguration() = config - } - - override predicate isSource() { none() } - - override string toString() { result = sourceGroup } - - override 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 - } -} - -private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { - string sinkGroup; - Configuration config; - - PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { none() } - - override predicate isSource() { none() } - - override string toString() { result = sinkGroup } - - override 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 - } -} - -private predicate pathNode( - PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, - Configuration conf, LocalCallContext localCC -) { - midnode = mid.getNodeEx() and - state = mid.getState() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - midnode.getEnclosingCallable()) and - ap = mid.getAp() -} - -/** - * Holds if data may flow from `mid` to `node`. The last step in or out of - * a callable is recorded by `cc`. - */ -pragma[assume_small_delta] -pragma[nomagic] -private predicate pathStep( - PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap -) { - exists(NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC | - pathNode(mid, midnode, state0, cc, sc, ap, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, true, _, conf, localCC) - ) - or - exists( - AccessPath ap0, NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC - | - pathNode(mid, midnode, state0, cc, sc, ap0, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), conf, - localCC) and - ap0 instanceof AccessPathNil - ) - or - jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - ap = mid.getAp() - or - additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, mid.getConfiguration()) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - pathIntoCallable(mid, node, state, _, cc, sc, _, _) and ap = mid.getAp() - or - pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone - or - pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() -} - -pragma[nomagic] -private predicate pathReadStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - tc = ap0.getHead() and - Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -pragma[nomagic] -private predicate pathStoreStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -private predicate pathOutOfCallable0( - PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa, - Configuration config -) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - apa = mid.getAp().getApprox() and - config = mid.getConfiguration() -} - -pragma[nomagic] -private predicate pathOutOfCallable1( - PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPathApprox apa, Configuration config -) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - pathOutOfCallable0(mid, pos, state, innercc, apa, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() +private predicate hasFlow(Node source, Node sink, Configuration config) { + exists(PathNode source0, PathNode sink0 | + hasFlowPath(source0, sink0, config) and + source0.getNode() = source and + sink0.getNode() = sink ) } -pragma[noinline] -private NodeEx getAnOutNodeFlow( - ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config -) { - result.asNode() = kind.getAnOutNode(call) and - Stage5::revFlow(result, _, apa, config) +private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) { + hasFlowPath(source, sink) and source.getConfiguration() = config } -/** - * Holds if data may flow from `mid` to `out`. The last step of this path - * is a return from a callable and is recorded by `cc`, if needed. - */ -pragma[noinline] -private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { - exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | - pathOutOfCallable1(mid, call, kind, state, cc, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} +private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) } -/** - * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. - */ -pragma[noinline] -private predicate pathIntoArg( - PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(ArgNodeEx arg, ArgumentPosition apos | - pathNode(mid, arg, state, cc, _, ap, config, _) and - arg.asNode().(ArgNode).argumentOf(call, apos) and - apa = ap.getApprox() and - parameterMatch(ppos, apos) - ) -} - -pragma[nomagic] -private predicate parameterCand( - DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::revFlow(p, _, apa, config) and - p.isParameterOf(callable, pos) - ) -} - -pragma[nomagic] -private predicate pathIntoCallable0( - PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, AccessPath ap, Configuration config -) { - exists(AccessPathApprox apa | - pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - callable = resolveCall(call, outercc) and - parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa), - pragma[only_bind_into](config)) - ) -} - -/** - * Holds if data may flow from `mid` to `p` through `call`. The contexts - * before and after entering the callable are `outercc` and `innercc`, - * respectively. - */ -pragma[nomagic] -private predicate pathIntoCallable( - PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, - SummaryCtx sc, DataFlowCall call, Configuration config -) { - exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | - pathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - ( - sc = TSummaryCtxSome(p, state, ap) - or - not exists(TSummaryCtxSome(p, state, ap)) and - sc = TSummaryCtxNone() and - // When the call contexts of source and sink needs to match then there's - // never any reason to enter a callable except to find a summary. See also - // the comment in `PathNodeMid::isAtSink`. - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) -} - -/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ -pragma[nomagic] -private predicate paramFlowsThrough( - ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, - AccessPathApprox apa, Configuration config -) { - exists(RetNodeEx ret | - pathNode(_, ret, state, cc, sc, ap, config, _) and - kind = ret.getKind() and - apa = ap.getApprox() and - parameterFlowThroughAllowed(sc.getParamNode(), kind) - ) -} - -pragma[nomagic] -private predicate pathThroughCallable0( - DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(CallContext innercc, SummaryCtx sc | - pathIntoCallable(mid, _, _, cc, innercc, sc, call, config) and - paramFlowsThrough(kind, state, innercc, sc, ap, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` through a callable to the node `out`. - * The context `cc` is restored to its value prior to entering the callable. - */ -pragma[noinline] -private predicate pathThroughCallable( - PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap -) { - exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config | - pathThroughCallable0(call, mid, kind, state, cc, ap, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -private module Subpaths { - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths01( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - exists(Configuration config | - pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and - pathIntoCallable(arg, par, _, _, innercc, sc, _, config) and - paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, - pragma[only_bind_into](apout), _, unbindConf(config)) and - not arg.isHidden() - ) - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `sout`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths02( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and - out.asNode() = kind.getAnOutNode(_) - } - - pragma[nomagic] - private Configuration getPathNodeConf(PathNodeImpl n) { result = n.getConfiguration() } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple. - */ - pragma[nomagic] - private predicate subpaths03( - PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, AccessPath apout - ) { - exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | - subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and - pathNode(ret, retnode, sout, innercc, sc, apout, unbindConf(getPathNodeConf(arg)), _) and - kind = retnode.getKind() - ) - } - - private PathNodeImpl localStepToHidden(PathNodeImpl n) { - n.getASuccessorImpl() = result and - result.isHidden() and - exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | - localFlowBigStep(n1, _, n2, _, _, _, _, _) or - store(n1, _, n2, _, _) or - readSet(n1, _, n2, _) - ) - } - - pragma[nomagic] - private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { - succ = pred.getANonHiddenSuccessor() and - succNode = succ.getNodeEx() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { - exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | - pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and - subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and - hasSuccessor(pragma[only_bind_into](arg), par, p) and - not ret.isHidden() and - pathNode(out0, o, sout, _, _, apout, _, _) - | - out = out0 or out = out0.projectToSink() - ) - } - - /** - * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. - */ - predicate retReach(PathNodeImpl n) { - exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) - or - exists(PathNodeImpl mid | - retReach(mid) and - n.getANonHiddenSuccessor() = mid and - not subpaths(_, mid, _, _) - ) - } -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -private predicate hasFlowPath( - PathNodeImpl flowsource, PathNodeImpl flowsink, Configuration configuration -) { - flowsource.isFlowSource() and - flowsource.getConfiguration() = configuration and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.isFlowSink() -} - -private predicate flowsTo( - PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink, - Configuration configuration -) { - flowsource.isSource() and - flowsource.getConfiguration() = configuration and - flowsource.getNodeEx().asNode() = source and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.getNodeEx().asNode() = sink -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -predicate flowsTo(Node source, Node sink, Configuration configuration) { - flowsTo(_, _, source, sink, configuration) -} - -private predicate finalStats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples -) { - fwd = true and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and - tuples = count(PathNodeImpl pn) - or - fwd = false and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and - tuples = count(PathNode pn) -} - -/** - * INTERNAL: Only for debugging. - * - * Calculates per-stage metrics for data flow. - */ -predicate stageStats( - int n, string stage, int nodes, int fields, int conscand, int states, int tuples, - Configuration config -) { - stage = "1 Fwd" and - n = 10 and - Stage1::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "1 Rev" and - n = 15 and - Stage1::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Fwd" and - n = 20 and - Stage2::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Rev" and - n = 25 and - Stage2::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Fwd" and - n = 30 and - Stage3::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Rev" and - n = 35 and - Stage3::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Fwd" and - n = 40 and - Stage4::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Rev" and - n = 45 and - Stage4::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Fwd" and - n = 50 and - Stage5::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Rev" and - n = 55 and - Stage5::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) - or - stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) -} - -private module FlowExploration { - private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - jumpStep(node1, node2, config) - or - additionalJumpStep(node1, node2, config) - or - additionalJumpStateStep(node1, _, node2, _, config) - or - // flow into callable - viableParamArgEx(_, node2, node1) - or - // flow out of a callable - viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) - | - c1 = node1.getEnclosingCallable() and - c2 = node2.getEnclosingCallable() and - c1 != c2 - ) - } - - private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSource(n) or config.isSource(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSrc(mid, config) and callableStep(mid, c, config) - ) - } - - private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSink(n) or config.isSink(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSink(mid, config) and callableStep(c, mid, config) - ) - } - - private newtype TCallableExt = - TCallable(DataFlowCallable c, Configuration config) { - interestingCallableSrc(c, config) or - interestingCallableSink(c, config) - } or - TCallableSrc() or - TCallableSink() - - private predicate callableExtSrc(TCallableSrc src) { any() } - - private predicate callableExtSink(TCallableSink sink) { any() } - - private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { - exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | - callableStep(c1, c2, config) and - ce1 = TCallable(c1, pragma[only_bind_into](config)) and - ce2 = TCallable(c2, pragma[only_bind_into](config)) - ) - or - exists(Node n, Configuration config | - ce1 = TCallableSrc() and - (config.isSource(n) or config.isSource(n, _)) and - ce2 = TCallable(getNodeEnclosingCallable(n), config) - ) - or - exists(Node n, Configuration config | - ce2 = TCallableSink() and - (config.isSink(n) or config.isSink(n, _)) and - ce1 = TCallable(getNodeEnclosingCallable(n), config) - ) - } - - private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { - callableExtStepFwd(ce2, ce1) - } - - private int distSrcExt(TCallableExt c) = - shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) - - private int distSinkExt(TCallableExt c) = - shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) - - private int distSrc(DataFlowCallable c, Configuration config) { - result = distSrcExt(TCallable(c, config)) - 1 - } - - private int distSink(DataFlowCallable c, Configuration config) { - result = distSinkExt(TCallable(c, config)) - 1 - } - - private newtype TPartialAccessPath = - TPartialNil(DataFlowType t) or - TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first - * element of the list and its length are tracked. If data flows from a source to - * a given node with a given `AccessPath`, this indicates the sequence of - * dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ - private class PartialAccessPath extends TPartialAccessPath { - abstract string toString(); - - TypedContent getHead() { this = TPartialCons(result, _) } - - int len() { - this = TPartialNil(_) and result = 0 - or - this = TPartialCons(_, result) - } - - DataFlowType getType() { - this = TPartialNil(result) - or - exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) - } - } - - private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { - override string toString() { - exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) - } - } - - private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { - override string toString() { - exists(TypedContent tc, int len | this = TPartialCons(tc, len) | - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private newtype TRevPartialAccessPath = - TRevPartialNil() or - TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `Content`s, but only the first - * element of the list and its length are tracked. - */ - private class RevPartialAccessPath extends TRevPartialAccessPath { - abstract string toString(); - - Content getHead() { this = TRevPartialCons(result, _) } - - int len() { - this = TRevPartialNil() and result = 0 - or - this = TRevPartialCons(_, result) - } - } - - private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { - override string toString() { result = "" } - } - - private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { - override string toString() { - exists(Content c, int len | this = TRevPartialCons(c, len) | - if len = 1 - then result = "[" + c.toString() + "]" - else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private predicate relevantState(FlowState state) { - sourceNode(_, state, _) or - sinkNode(_, state, _) or - additionalLocalStateStep(_, state, _, _, _) or - additionalLocalStateStep(_, _, _, state, _) or - additionalJumpStateStep(_, state, _, _, _) or - additionalJumpStateStep(_, _, _, state, _) - } - - private newtype TSummaryCtx1 = - TSummaryCtx1None() or - TSummaryCtx1Param(ParamNodeEx p) - - private newtype TSummaryCtx2 = - TSummaryCtx2None() or - TSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TSummaryCtx3 = - TSummaryCtx3None() or - TSummaryCtx3Some(PartialAccessPath ap) - - private newtype TRevSummaryCtx1 = - TRevSummaryCtx1None() or - TRevSummaryCtx1Some(ReturnPosition pos) - - private newtype TRevSummaryCtx2 = - TRevSummaryCtx2None() or - TRevSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TRevSummaryCtx3 = - TRevSummaryCtx3None() or - TRevSummaryCtx3Some(RevPartialAccessPath ap) - - private newtype TPartialPathNode = - TPartialPathNodeFwd( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = TPartialNil(node.getDataFlowType()) and - exists(config.explorationLimit()) - or - partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap, config) and - distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() - } or - TPartialPathNodeRev( - NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, - RevPartialAccessPath ap, Configuration config - ) { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() and - exists(config.explorationLimit()) - or - revPartialPathStep(_, node, state, sc1, sc2, sc3, ap, config) and - not clearsContentEx(node, ap.getHead()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead()) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() - } - - pragma[nomagic] - private predicate partialPathNodeMk0( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap, config) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - not clearsContentEx(node, ap.getHead().getContent()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead().getContent()) - ) and - if node.asNode() instanceof CastingNode - then compatibleTypes(node.getDataFlowType(), ap.getType()) - else any() - } - - /** - * A `Node` augmented with a call context, an access path, and a configuration. - */ - class PartialPathNode extends TPartialPathNode { - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() - } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - /** Gets the underlying `Node`. */ - final Node getNode() { this.getNodeEx().projectToNode() = result } - - FlowState getState() { none() } - - private NodeEx getNodeEx() { - result = this.(PartialPathNodeFwd).getNodeEx() or - result = this.(PartialPathNodeRev).getNodeEx() - } - - /** Gets the associated configuration. */ - Configuration getConfiguration() { none() } - - /** Gets a successor of this node, if any. */ - PartialPathNode getASuccessor() { none() } - - /** - * Gets the approximate distance to the nearest source measured in number - * of interprocedural steps. - */ - int getSourceDistance() { - result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - /** - * Gets the approximate distance to the nearest sink measured in number - * of interprocedural steps. - */ - int getSinkDistance() { - result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - private string ppAp() { - exists(string s | - s = this.(PartialPathNodeFwd).getAp().toString() or - s = this.(PartialPathNodeRev).getAp().toString() - | - if s = "" then result = "" else result = " " + s - ) - } - - private string ppCtx() { - result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" - } - - /** Holds if this is a source in a forward-flow path. */ - predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } - - /** Holds if this is a sink in a reverse-flow path. */ - predicate isRevSink() { this.(PartialPathNodeRev).isSink() } - } - - /** - * Provides the query predicates needed to include a graph in a path-problem query. - */ - module PartialPathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } - } - - private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { - NodeEx node; - FlowState state; - CallContext cc; - TSummaryCtx1 sc1; - TSummaryCtx2 sc2; - TSummaryCtx3 sc3; - PartialAccessPath ap; - Configuration config; - - PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - TSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TSummaryCtx3 getSummaryCtx3() { result = sc3 } - - PartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeFwd getASuccessor() { - partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp(), - result.getConfiguration()) - } - - predicate isSource() { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap instanceof TPartialNil - } - } - - private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { - NodeEx node; - FlowState state; - TRevSummaryCtx1 sc1; - TRevSummaryCtx2 sc2; - TRevSummaryCtx3 sc3; - RevPartialAccessPath ap; - Configuration config; - - PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } - - RevPartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeRev getASuccessor() { - revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), - this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp(), this.getConfiguration()) - } - - predicate isSink() { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() - } - } - - private predicate partialPathStep( - PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, - TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and - ( - localFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - ) - or - jumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - partialPathStoreStep(mid, _, _, node, ap) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(PartialAccessPath ap0, TypedContent tc | - partialPathReadStep(mid, ap0, tc, node, cc, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsFwd(ap, tc, ap0, config) - ) - or - partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap, config) - or - partialPathOutOfCallable(mid, node, state, cc, ap, config) and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() - or - partialPathThroughCallable(mid, node, state, cc, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - bindingset[result, i] - private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } - - pragma[inline] - private predicate partialPathStoreStep( - PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, - PartialAccessPath ap2 - ) { - exists(NodeEx midNode, DataFlowType contentType | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - store(midNode, tc, node, contentType, mid.getConfiguration()) and - ap2.getHead() = tc and - ap2.len() = unbindInt(ap1.len() + 1) and - compatibleTypes(ap1.getType(), contentType) - ) - } - - pragma[nomagic] - private predicate apConsFwd( - PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeFwd mid | - partialPathStoreStep(mid, ap1, tc, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate partialPathReadStep( - PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc, - Configuration config - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and - ap.getHead() = tc and - pragma[only_bind_into](config) = mid.getConfiguration() and - cc = mid.getCallContext() - ) - } - - private predicate partialPathOutOfCallable0( - PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, - PartialAccessPath ap, Configuration config - ) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - ap = mid.getAp() and - config = mid.getConfiguration() - } - - pragma[nomagic] - private predicate partialPathOutOfCallable1( - PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - partialPathOutOfCallable0(mid, pos, state, innercc, ap, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) - } - - private predicate partialPathOutOfCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(ReturnKindExt kind, DataFlowCall call | - partialPathOutOfCallable1(mid, call, kind, state, cc, ap, config) - | - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[noinline] - private predicate partialPathIntoArg( - PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ArgNode arg, ArgumentPosition apos | - arg = mid.getNodeEx().asNode() and - state = mid.getState() and - cc = mid.getCallContext() and - arg.argumentOf(call, apos) and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate partialPathIntoCallable0( - PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - partialPathIntoArg(mid, pos, state, outercc, call, ap, config) and - callable = resolveCall(call, outercc) - } - - private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, - CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ParameterPosition pos, DataFlowCallable callable | - partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - sc1 = TSummaryCtx1Param(p) and - sc2 = TSummaryCtx2Some(state) and - sc3 = TSummaryCtx3Some(ap) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) - } - - pragma[nomagic] - private predicate paramFlowsThroughInPartialPath( - ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeFwd mid, RetNodeEx ret | - mid.getNodeEx() = ret and - kind = ret.getKind() and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() and - ap = mid.getAp() - ) - } - - pragma[noinline] - private predicate partialPathThroughCallable0( - DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | - partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _, config) and - paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap, config) - ) - } - - private predicate partialPathThroughCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ReturnKindExt kind | - partialPathThroughCallable0(call, mid, kind, state, cc, ap, config) and - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[nomagic] - private predicate revPartialPathStep( - PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, - TRevSummaryCtx3 sc3, RevPartialAccessPath ap, Configuration config - ) { - localFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - jumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - revPartialPathReadStep(mid, _, _, node, ap) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(RevPartialAccessPath ap0, Content c | - revPartialPathStoreStep(mid, ap0, c, node, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsRev(ap, c, ap0, config) - ) - or - exists(ParamNodeEx p | - mid.getNodeEx() = p and - viableParamArgEx(_, p, node) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - or - exists(ReturnPosition pos | - revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap, config) and - pos = getReturnPosition(node.asNode()) - ) - or - revPartialPathThroughCallable(mid, node, state, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - pragma[inline] - private predicate revPartialPathReadStep( - PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, - RevPartialAccessPath ap2 - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - read(node, c, midNode, mid.getConfiguration()) and - ap2.getHead() = c and - ap2.len() = unbindInt(ap1.len() + 1) - ) - } - - pragma[nomagic] - private predicate apConsRev( - RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeRev mid | - revPartialPathReadStep(mid, ap1, c, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathStoreStep( - PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config - ) { - exists(NodeEx midNode, TypedContent tc | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - store(node, tc, midNode, _, config) and - ap.getHead() = c and - config = mid.getConfiguration() and - tc.getContent() = c - ) - } - - pragma[nomagic] - private predicate revPartialPathIntoReturn( - PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, - TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap, - Configuration config - ) { - exists(NodeEx out | - mid.getNodeEx() = out and - mid.getState() = state and - viableReturnPosOutEx(call, pos, out) and - sc1 = TRevSummaryCtx1Some(pos) and - sc2 = TRevSummaryCtx2Some(state) and - sc3 = TRevSummaryCtx3Some(ap) and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathFlowsThrough( - ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, - TRevSummaryCtx3Some sc3, RevPartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | - mid.getNodeEx() = p and - mid.getState() = state and - p.getPosition() = ppos and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable0( - DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, - RevPartialAccessPath ap, Configuration config - ) { - exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | - revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _, config) and - revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap, config) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ArgumentPosition pos | - revPartialPathThroughCallable0(call, mid, pos, state, ap, config) and - node.asNode().(ArgNode).argumentOf(call, pos) - ) - } -} - -import FlowExploration - -private predicate partialFlow( - PartialPathNode source, PartialPathNode node, Configuration configuration -) { - source.getConfiguration() = configuration and - source.isFwdSource() and - node = source.getASuccessor+() -} - -private predicate revPartialFlow( - PartialPathNode node, PartialPathNode sink, Configuration configuration -) { - sink.getConfiguration() = configuration and - sink.isRevSink() and - node.getASuccessor+() = sink -} +predicate flowsTo = hasFlow/3; diff --git a/go/ql/lib/semmle/go/dataflow/internal/DataFlowUtil.qll b/go/ql/lib/semmle/go/dataflow/internal/DataFlowUtil.qll index e742493b5ca..40ec012cc27 100644 --- a/go/ql/lib/semmle/go/dataflow/internal/DataFlowUtil.qll +++ b/go/ql/lib/semmle/go/dataflow/internal/DataFlowUtil.qll @@ -2,8 +2,8 @@ * Provides Go-specific definitions for use in the data flow library. */ -import go -import semmle.go.dataflow.FunctionInputsAndOutputs +private import go +private import semmle.go.dataflow.FunctionInputsAndOutputs private import semmle.go.dataflow.ExternalFlow private import DataFlowPrivate private import FlowSummaryImpl as FlowSummaryImpl diff --git a/go/ql/lib/semmle/go/dataflow/internal/FlowSummaryImpl.qll b/go/ql/lib/semmle/go/dataflow/internal/FlowSummaryImpl.qll index 85ffeb62e5f..468ed73e81a 100644 --- a/go/ql/lib/semmle/go/dataflow/internal/FlowSummaryImpl.qll +++ b/go/ql/lib/semmle/go/dataflow/internal/FlowSummaryImpl.qll @@ -248,7 +248,9 @@ module Public { /** * Holds if all the summaries that apply to `this` are auto generated and not manually created. */ - final predicate isAutoGenerated() { this.hasProvenance("generated") and not this.isManual() } + final predicate isAutoGenerated() { + this.hasProvenance(["generated", "ai-generated"]) and not this.isManual() + } /** * Holds if there exists a manual summary that applies to `this`. @@ -268,7 +270,7 @@ module Public { /** * Holds if the neutral is auto generated. */ - predicate isAutoGenerated() { neutralElement(this, "generated") } + predicate isAutoGenerated() { neutralElement(this, ["generated", "ai-generated"]) } /** * Holds if there exists a manual neutral that applies to `this`. @@ -1202,11 +1204,11 @@ module Private { } private string renderProvenance(SummarizedCallable c) { - if c.isAutoGenerated() then result = "generated" else result = "manual" + if c.isManual() then result = "manual" else c.hasProvenance(result) } private string renderProvenanceNeutral(NeutralCallable c) { - if c.isAutoGenerated() then result = "generated" else result = "manual" + if c.isManual() then result = "manual" else c.hasProvenance(result) } /** diff --git a/go/ql/lib/semmle/go/dataflow/internal/tainttracking1/TaintTracking.qll b/go/ql/lib/semmle/go/dataflow/internal/tainttracking1/TaintTracking.qll new file mode 100644 index 00000000000..7333264298e --- /dev/null +++ b/go/ql/lib/semmle/go/dataflow/internal/tainttracking1/TaintTracking.qll @@ -0,0 +1,63 @@ +/** + * Provides classes for performing local (intra-procedural) and + * global (inter-procedural) taint-tracking analyses. + */ + +import TaintTrackingParameter::Public +private import TaintTrackingParameter::Private + +private module AddTaintDefaults implements +DataFlowInternal::FullStateConfigSig { + import Config + + predicate isBarrier(DataFlow::Node node) { + Config::isBarrier(node) or defaultTaintSanitizer(node) + } + + predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + Config::isAdditionalFlowStep(node1, node2) or + defaultAdditionalTaintStep(node1, node2) + } + + predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) { + Config::allowImplicitRead(node, c) + or + ( + Config::isSink(node, _) or + Config::isAdditionalFlowStep(node, _) or + Config::isAdditionalFlowStep(node, _, _, _) + ) and + defaultImplicitTaintRead(node, c) + } +} + +/** + * Constructs a standard taint tracking computation. + */ +module Make implements DataFlow::DataFlowSig { + private module Config0 implements DataFlowInternal::FullStateConfigSig { + import DataFlowInternal::DefaultState + import Config + } + + private module C implements DataFlowInternal::FullStateConfigSig { + import AddTaintDefaults + } + + import DataFlowInternal::Impl +} + +/** + * Constructs a taint tracking computation using flow state. + */ +module MakeWithState implements DataFlow::DataFlowSig { + private module Config0 implements DataFlowInternal::FullStateConfigSig { + import Config + } + + private module C implements DataFlowInternal::FullStateConfigSig { + import AddTaintDefaults + } + + import DataFlowInternal::Impl +} diff --git a/go/ql/lib/semmle/go/dataflow/internal/tainttracking1/TaintTrackingParameter.qll b/go/ql/lib/semmle/go/dataflow/internal/tainttracking1/TaintTrackingParameter.qll index b38482194ec..a5a45514a06 100644 --- a/go/ql/lib/semmle/go/dataflow/internal/tainttracking1/TaintTrackingParameter.qll +++ b/go/ql/lib/semmle/go/dataflow/internal/tainttracking1/TaintTrackingParameter.qll @@ -2,4 +2,5 @@ import semmle.go.dataflow.internal.TaintTrackingUtil as Public module Private { import semmle.go.dataflow.DataFlow::DataFlow as DataFlow + import semmle.go.dataflow.internal.DataFlowImpl as DataFlowInternal } diff --git a/go/ql/lib/semmle/go/security/IncorrectIntegerConversionLib.qll b/go/ql/lib/semmle/go/security/IncorrectIntegerConversionLib.qll index 1c0b647e91f..e96ba8b687c 100644 --- a/go/ql/lib/semmle/go/security/IncorrectIntegerConversionLib.qll +++ b/go/ql/lib/semmle/go/security/IncorrectIntegerConversionLib.qll @@ -134,11 +134,11 @@ class ConversionWithoutBoundsCheckConfig extends TaintTracking::Configuration { node = DataFlow::BarrierGuard::getABarrierNodeForGuard(g) and g.isBoundFor(bitSize, sinkIsSigned) ) - } - - override predicate isSanitizerOut(DataFlow::Node node) { - exists(int bitSize | isIncorrectIntegerConversion(sourceBitSize, bitSize) | - this.isSinkWithBitSize(node, bitSize) + or + exists(DataFlow::Node sink, int bitSize | + isIncorrectIntegerConversion(sourceBitSize, bitSize) and + this.isSinkWithBitSize(sink, bitSize) and + TaintTracking::localTaintStep(sink, node) ) } } diff --git a/go/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/Flows.ql b/go/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/Flows.ql index 176396da6f7..87e9b301148 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/Flows.ql +++ b/go/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/Flows.ql @@ -41,8 +41,6 @@ class DataConfiguration extends DataFlow::Configuration { override predicate isSink(DataFlow::Node sink) { sink = any(DataFlow::CallNode c | c.getCalleeName() = "sink").getArgument(0) } - - override int explorationLimit() { result = 10 } // this is different! } class DataFlowTest extends InlineExpectationsTest { @@ -71,8 +69,6 @@ class TaintConfiguration extends TaintTracking::Configuration { override predicate isSink(DataFlow::Node sink) { sink = any(DataFlow::CallNode c | c.getCalleeName() = "sink").getArgument(0) } - - override int explorationLimit() { result = 10 } // this is different! } class TaintFlowTest extends InlineExpectationsTest { diff --git a/go/ql/test/query-tests/Security/CWE-918/RequestForgery.expected b/go/ql/test/query-tests/Security/CWE-918/RequestForgery.expected index d6bb8bbf7af..74ec75c02f3 100644 --- a/go/ql/test/query-tests/Security/CWE-918/RequestForgery.expected +++ b/go/ql/test/query-tests/Security/CWE-918/RequestForgery.expected @@ -6,13 +6,7 @@ edges | tst.go:10:13:10:35 | call to FormValue | tst.go:24:66:24:72 | tainted | | tst.go:10:13:10:35 | call to FormValue | tst.go:27:11:27:29 | ...+... | | tst.go:10:13:10:35 | call to FormValue | tst.go:29:11:29:40 | ...+... | -| tst.go:10:13:10:35 | call to FormValue | tst.go:36:2:36:2 | implicit dereference | | tst.go:10:13:10:35 | call to FormValue | tst.go:37:11:37:20 | call to String | -| tst.go:35:2:35:2 | definition of u [pointer] | tst.go:36:2:36:2 | u [pointer] | -| tst.go:36:2:36:2 | implicit dereference | tst.go:35:2:35:2 | definition of u [pointer] | -| tst.go:36:2:36:2 | implicit dereference | tst.go:36:2:36:2 | implicit dereference | -| tst.go:36:2:36:2 | implicit dereference | tst.go:37:11:37:20 | call to String | -| tst.go:36:2:36:2 | u [pointer] | tst.go:36:2:36:2 | implicit dereference | | websocket.go:60:21:60:31 | call to Referer | websocket.go:65:27:65:40 | untrustedInput | | websocket.go:74:21:74:31 | call to Referer | websocket.go:78:36:78:49 | untrustedInput | | websocket.go:88:21:88:31 | call to Referer | websocket.go:91:31:91:44 | untrustedInput | @@ -32,9 +26,6 @@ nodes | tst.go:24:66:24:72 | tainted | semmle.label | tainted | | tst.go:27:11:27:29 | ...+... | semmle.label | ...+... | | tst.go:29:11:29:40 | ...+... | semmle.label | ...+... | -| tst.go:35:2:35:2 | definition of u [pointer] | semmle.label | definition of u [pointer] | -| tst.go:36:2:36:2 | implicit dereference | semmle.label | implicit dereference | -| tst.go:36:2:36:2 | u [pointer] | semmle.label | u [pointer] | | tst.go:37:11:37:20 | call to String | semmle.label | call to String | | websocket.go:60:21:60:31 | call to Referer | semmle.label | call to Referer | | websocket.go:65:27:65:40 | untrustedInput | semmle.label | untrustedInput | diff --git a/java/documentation/library-coverage/coverage.csv b/java/documentation/library-coverage/coverage.csv index fbdd50b2091..211ecabe415 100644 --- a/java/documentation/library-coverage/coverage.csv +++ b/java/documentation/library-coverage/coverage.csv @@ -39,8 +39,8 @@ jakarta.ws.rs.container,,9,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,9,, jakarta.ws.rs.core,2,,149,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,94,55 java.beans,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, java.io,37,,42,,15,,,,,,,,,,,,,,,,,,,,,,,,,,,,22,,,,,,,,41,1 -java.lang,13,,76,,,,,,,,,,,,8,,,,,,,4,,,1,,,,,,,,,,,,,,,,53,23 -java.net,10,3,7,,,,,,,,,,,,,,,10,,,,,,,,,,,,,,,,,,,,,,3,7, +java.lang,14,,76,,,,,,,,,,,,8,,,,,1,,4,,,1,,,,,,,,,,,,,,,,53,23 +java.net,10,3,9,,,,,,,,,,,,,,,10,,,,,,,,,,,,,,,,,,,,,,3,9, java.nio,16,,16,,13,,,,,,,,,,,,,,,1,,,,,,,,,,,,,2,,,,,,,,16, java.sql,11,,2,,,,,,,,4,,,,,,,,,,,,,,,,,,7,,,,,,,,,,,,1,1 java.util,44,,465,,,,,,,,,,,,34,,,,,,,,5,2,,1,2,,,,,,,,,,,,,,38,427 @@ -74,10 +74,12 @@ org.apache.commons.logging,6,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,,,,,,,,,,, org.apache.commons.ognl,6,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,,,,,,,,, org.apache.commons.text,,,272,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,220,52 org.apache.directory.ldap.client.api,1,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.apache.hadoop.hive.metastore,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,3,,,,,,,,,,,,, org.apache.hc.core5.function,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, org.apache.hc.core5.http,1,2,39,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,2,39, org.apache.hc.core5.net,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2, org.apache.hc.core5.util,,,24,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,18,6 +org.apache.hive.hcatalog.templeton,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,, org.apache.http,27,3,70,,,,,,,,,,,,,,,25,,,,,,,,,,,,,,,,,,2,,,,3,62,8 org.apache.ibatis.jdbc,6,,57,,,,,,,,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,57, org.apache.log4j,11,,,,,,,,,,,,,,11,,,,,,,,,,,,,,,,,,,,,,,,,,, diff --git a/java/documentation/library-coverage/coverage.rst b/java/documentation/library-coverage/coverage.rst index 59c21e3ed29..2e90702b3b8 100644 --- a/java/documentation/library-coverage/coverage.rst +++ b/java/documentation/library-coverage/coverage.rst @@ -18,10 +18,10 @@ Java framework & library support `Google Guava `_,``com.google.common.*``,,728,39,,6,,,,, JBoss Logging,``org.jboss.logging``,,,324,,,,,,, `JSON-java `_,``org.json``,,236,,,,,,,, - Java Standard Library,``java.*``,3,609,131,28,,,7,,,10 + Java Standard Library,``java.*``,3,611,132,28,,,7,,,10 Java extensions,"``javax.*``, ``jakarta.*``",63,609,32,,,4,,1,1,2 Kotlin Standard Library,``kotlin*``,,1835,12,10,,,,,,2 `Spring `_,``org.springframework.*``,29,477,101,,,,19,14,,29 - Others,"``cn.hutool.core.codec``, ``com.esotericsoftware.kryo.io``, ``com.esotericsoftware.kryo5.io``, ``com.fasterxml.jackson.core``, ``com.fasterxml.jackson.databind``, ``com.hubspot.jinjava``, ``com.mitchellbosecke.pebble``, ``com.opensymphony.xwork2.ognl``, ``com.rabbitmq.client``, ``com.unboundid.ldap.sdk``, ``com.zaxxer.hikari``, ``flexjson``, ``freemarker.cache``, ``freemarker.template``, ``groovy.lang``, ``groovy.util``, ``jodd.json``, ``net.sf.saxon.s9api``, ``ognl``, ``okhttp3``, ``org.apache.commons.codec``, ``org.apache.commons.jexl2``, ``org.apache.commons.jexl3``, ``org.apache.commons.logging``, ``org.apache.commons.ognl``, ``org.apache.directory.ldap.client.api``, ``org.apache.ibatis.jdbc``, ``org.apache.log4j``, ``org.apache.shiro.codec``, ``org.apache.shiro.jndi``, ``org.apache.velocity.app``, ``org.apache.velocity.runtime``, ``org.codehaus.groovy.control``, ``org.dom4j``, ``org.hibernate``, ``org.jdbi.v3.core``, ``org.jooq``, ``org.mvel2``, ``org.scijava.log``, ``org.slf4j``, ``org.thymeleaf``, ``org.xml.sax``, ``org.xmlpull.v1``, ``play.mvc``, ``ratpack.core.form``, ``ratpack.core.handling``, ``ratpack.core.http``, ``ratpack.exec``, ``ratpack.form``, ``ratpack.func``, ``ratpack.handling``, ``ratpack.http``, ``ratpack.util``, ``retrofit2``",60,300,269,,,,14,18,,3 - Totals,,217,8456,1564,129,6,10,107,33,1,86 + Others,"``cn.hutool.core.codec``, ``com.esotericsoftware.kryo.io``, ``com.esotericsoftware.kryo5.io``, ``com.fasterxml.jackson.core``, ``com.fasterxml.jackson.databind``, ``com.hubspot.jinjava``, ``com.mitchellbosecke.pebble``, ``com.opensymphony.xwork2.ognl``, ``com.rabbitmq.client``, ``com.unboundid.ldap.sdk``, ``com.zaxxer.hikari``, ``flexjson``, ``freemarker.cache``, ``freemarker.template``, ``groovy.lang``, ``groovy.util``, ``jodd.json``, ``net.sf.saxon.s9api``, ``ognl``, ``okhttp3``, ``org.apache.commons.codec``, ``org.apache.commons.jexl2``, ``org.apache.commons.jexl3``, ``org.apache.commons.logging``, ``org.apache.commons.ognl``, ``org.apache.directory.ldap.client.api``, ``org.apache.hadoop.hive.metastore``, ``org.apache.hive.hcatalog.templeton``, ``org.apache.ibatis.jdbc``, ``org.apache.log4j``, ``org.apache.shiro.codec``, ``org.apache.shiro.jndi``, ``org.apache.velocity.app``, ``org.apache.velocity.runtime``, ``org.codehaus.groovy.control``, ``org.dom4j``, ``org.hibernate``, ``org.jdbi.v3.core``, ``org.jooq``, ``org.mvel2``, ``org.scijava.log``, ``org.slf4j``, ``org.thymeleaf``, ``org.xml.sax``, ``org.xmlpull.v1``, ``play.mvc``, ``ratpack.core.form``, ``ratpack.core.handling``, ``ratpack.core.http``, ``ratpack.exec``, ``ratpack.form``, ``ratpack.func``, ``ratpack.handling``, ``ratpack.http``, ``ratpack.util``, ``retrofit2``",60,300,273,,,,18,18,,3 + Totals,,217,8458,1569,129,6,10,111,33,1,86 diff --git a/java/ql/integration-tests/all-platforms/java/diagnostics/android-gradle-incompatibility/diagnostics.expected b/java/ql/integration-tests/all-platforms/java/diagnostics/android-gradle-incompatibility/diagnostics.expected index 29e2215412e..5446916799d 100644 --- a/java/ql/integration-tests/all-platforms/java/diagnostics/android-gradle-incompatibility/diagnostics.expected +++ b/java/ql/integration-tests/all-platforms/java/diagnostics/android-gradle-incompatibility/diagnostics.expected @@ -1,5 +1,5 @@ { - "markdownMessage": "An Android build may have failed. Ensure the Code Scanning workflow installs required dependencies, and that the [Gradle and Android SDK versions are compatible](https://developer.android.com/studio/releases/gradle-plugin#updating-gradle). Suspicious output line: ` > Minimum supported Gradle version is 7.4. Current version is 7.3. If using the gradle wrapper, try editing the distributionUrl in /gradle/wrapper/gradle-wrapper.properties to gradle-7.4-all.zip`", + "markdownMessage": "An Android build may have failed. Ensure the Code Scanning workflow installs required dependencies, and that the [Gradle and Android SDK versions are compatible](https://developer.android.com/studio/releases/gradle-plugin#updating-gradle).\n\nSuspicious output line: ` > Minimum supported Gradle version is 7.4. Current version is 7.3. If using the gradle wrapper, try editing the distributionUrl in /gradle/wrapper/gradle-wrapper.properties to gradle-7.4-all.zip`", "severity": "error", "source": { "extractorName": "java", @@ -13,7 +13,7 @@ } } { - "markdownMessage": "An Android build may have failed. Ensure the Code Scanning workflow installs required dependencies, and that the [Gradle and Android SDK versions are compatible](https://developer.android.com/studio/releases/gradle-plugin#updating-gradle). Suspicious output line: `Caused by: java.lang.RuntimeException: Minimum supported Gradle version is 7.4. Current version is 7.3. If using the gradle wrapper, try editing the distributionUrl in /gradle/wrapper/gradle-wrapper.properties to gradle-7.4-all.zip`", + "markdownMessage": "An Android build may have failed. Ensure the Code Scanning workflow installs required dependencies, and that the [Gradle and Android SDK versions are compatible](https://developer.android.com/studio/releases/gradle-plugin#updating-gradle).\n\nSuspicious output line: `Caused by: java.lang.RuntimeException: Minimum supported Gradle version is 7.4. Current version is 7.3. If using the gradle wrapper, try editing the distributionUrl in /gradle/wrapper/gradle-wrapper.properties to gradle-7.4-all.zip`", "severity": "error", "source": { "extractorName": "java", diff --git a/java/ql/integration-tests/all-platforms/java/diagnostics/compilation-error/diagnostics.expected b/java/ql/integration-tests/all-platforms/java/diagnostics/compilation-error/diagnostics.expected index 81b15627115..34a7884823d 100644 --- a/java/ql/integration-tests/all-platforms/java/diagnostics/compilation-error/diagnostics.expected +++ b/java/ql/integration-tests/all-platforms/java/diagnostics/compilation-error/diagnostics.expected @@ -1,5 +1,5 @@ { - "markdownMessage": "A compilation error was observed while autobuilding your code. Check that your Code Scanning workflow installs the needed compiler version and dependencies. Suspicious output line: `[ERROR] COMPILATION ERROR : `", + "markdownMessage": "A compilation error was observed while autobuilding your code. Check that your Code Scanning workflow installs the needed compiler version and dependencies.\n\nSuspicious output line: `[ERROR] COMPILATION ERROR : `", "severity": "error", "source": { "extractorName": "java", @@ -13,7 +13,7 @@ } } { - "markdownMessage": "A compilation error was observed while autobuilding your code. Check that your Code Scanning workflow installs the needed compiler version and dependencies. Suspicious output line: `[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.0:compile (default-compile) on project maven-sample: Compilation failure`", + "markdownMessage": "A compilation error was observed while autobuilding your code. Check that your Code Scanning workflow installs the needed compiler version and dependencies.\n\nSuspicious output line: `[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.0:compile (default-compile) on project maven-sample: Compilation failure`", "severity": "error", "source": { "extractorName": "java", @@ -27,7 +27,7 @@ } } { - "markdownMessage": "A compilation error was observed while autobuilding your code. Check that your Code Scanning workflow installs the needed compiler version and dependencies. Suspicious output line: `org.apache.maven.lifecycle.LifecycleExecutionException: Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.0:compile (default-compile) on project maven-sample: Compilation failure`", + "markdownMessage": "A compilation error was observed while autobuilding your code. Check that your Code Scanning workflow installs the needed compiler version and dependencies.\n\nSuspicious output line: `org.apache.maven.lifecycle.LifecycleExecutionException: Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.0:compile (default-compile) on project maven-sample: Compilation failure`", "severity": "error", "source": { "extractorName": "java", diff --git a/java/ql/integration-tests/all-platforms/java/diagnostics/dependency-error/diagnostics.expected b/java/ql/integration-tests/all-platforms/java/diagnostics/dependency-error/diagnostics.expected index 21fa19216be..fbb583581f2 100644 --- a/java/ql/integration-tests/all-platforms/java/diagnostics/dependency-error/diagnostics.expected +++ b/java/ql/integration-tests/all-platforms/java/diagnostics/dependency-error/diagnostics.expected @@ -1,5 +1,5 @@ { - "markdownMessage": "A dependency failed to download. Check that all dependencies are available, and [supply credentials for any private dependencies](https://github.com/Azure/actions-workflow-samples/blob/master/assets/create-secrets-for-GitHub-workflows.md#set-up-secrets-in-github-action-workflows). Suspicious output line: `Caused by: org.eclipse.aether.transfer.ArtifactNotFoundException: Could not find artifact junit:junit-nonesuch:jar:4.11 in central (https://repo.maven.apache.org/maven2)`", + "markdownMessage": "A dependency failed to download. Check that all dependencies are available, and [supply credentials for any private dependencies](https://github.com/Azure/actions-workflow-samples/blob/master/assets/create-secrets-for-GitHub-workflows.md#set-up-secrets-in-github-action-workflows).\n\nSuspicious output line: `Caused by: org.eclipse.aether.transfer.ArtifactNotFoundException: Could not find artifact junit:junit-nonesuch:jar:4.11 in central (https://repo.maven.apache.org/maven2)`", "severity": "error", "source": { "extractorName": "java", diff --git a/java/ql/integration-tests/all-platforms/java/diagnostics/java-version-too-old/diagnostics.expected b/java/ql/integration-tests/all-platforms/java/diagnostics/java-version-too-old/diagnostics.expected index a48a1e1dd07..396198d057a 100644 --- a/java/ql/integration-tests/all-platforms/java/diagnostics/java-version-too-old/diagnostics.expected +++ b/java/ql/integration-tests/all-platforms/java/diagnostics/java-version-too-old/diagnostics.expected @@ -1,5 +1,5 @@ { - "markdownMessage": "Your project may need a different JDK version. Ensure your Code Scanning workflow file has [an appropriate `setup-java` step](https://github.com/actions/setup-java#eclipse-temurin). Suspicious output line: `> Could not target platform: 'Java SE 11' using tool chain: 'JDK 8 (1.8)'.`", + "markdownMessage": "Your project may need a different JDK version. Ensure your Code Scanning workflow file has [an appropriate `setup-java` step](https://github.com/actions/setup-java#eclipse-temurin).\n\nSuspicious output line: `> Could not target platform: 'Java SE 11' using tool chain: 'JDK 8 (1.8)'.`", "severity": "error", "source": { "extractorName": "java", @@ -13,7 +13,7 @@ } } { - "markdownMessage": "Your project may need a different JDK version. Ensure your Code Scanning workflow file has [an appropriate `setup-java` step](https://github.com/actions/setup-java#eclipse-temurin). Suspicious output line: `Caused by: java.lang.IllegalArgumentException: Could not target platform: 'Java SE 11' using tool chain: 'JDK 8 (1.8)'.`", + "markdownMessage": "Your project may need a different JDK version. Ensure your Code Scanning workflow file has [an appropriate `setup-java` step](https://github.com/actions/setup-java#eclipse-temurin).\n\nSuspicious output line: `Caused by: java.lang.IllegalArgumentException: Could not target platform: 'Java SE 11' using tool chain: 'JDK 8 (1.8)'.`", "severity": "error", "source": { "extractorName": "java", diff --git a/java/ql/integration-tests/all-platforms/java/diagnostics/maven-http-repository/diagnostics.expected b/java/ql/integration-tests/all-platforms/java/diagnostics/maven-http-repository/diagnostics.expected index 56c315303c1..301efee2929 100644 --- a/java/ql/integration-tests/all-platforms/java/diagnostics/maven-http-repository/diagnostics.expected +++ b/java/ql/integration-tests/all-platforms/java/diagnostics/maven-http-repository/diagnostics.expected @@ -1,5 +1,5 @@ { - "markdownMessage": "Building your code triggered an access to an insecure HTTP Maven repository. Allow access to insecure repositories, or [update your build to use HTTPS](https://maven.apache.org/docs/3.8.1/release-notes.html#how-to-fix-when-i-get-a-http-repository-blocked). Suspicious output line: `Caused by: org.eclipse.aether.resolution.ArtifactResolutionException: Could not transfer artifact junit-nonesuch:junit-nonesuch:pom:4.11 from/to maven-default-http-blocker (http://0.0.0.0/): Blocked mirror for repositories: [insecure (http://repo.maven.apache.org/maven2/, default, releases+snapshots)]`", + "markdownMessage": "Building your code triggered an access to an insecure HTTP Maven repository. Allow access to insecure repositories, or [update your build to use HTTPS](https://maven.apache.org/docs/3.8.1/release-notes.html#how-to-fix-when-i-get-a-http-repository-blocked).\n\nSuspicious output line: `Caused by: org.eclipse.aether.resolution.ArtifactResolutionException: Could not transfer artifact junit-nonesuch:junit-nonesuch:pom:4.11 from/to maven-default-http-blocker (http://0.0.0.0/): Blocked mirror for repositories: [insecure (http://repo.maven.apache.org/maven2/, default, releases+snapshots)]`", "severity": "error", "source": { "extractorName": "java", @@ -13,7 +13,7 @@ } } { - "markdownMessage": "Building your code triggered an access to an insecure HTTP Maven repository. Allow access to insecure repositories, or [update your build to use HTTPS](https://maven.apache.org/docs/3.8.1/release-notes.html#how-to-fix-when-i-get-a-http-repository-blocked). Suspicious output line: `Caused by: org.eclipse.aether.transfer.ArtifactTransferException: Could not transfer artifact junit-nonesuch:junit-nonesuch:pom:4.11 from/to maven-default-http-blocker (http://0.0.0.0/): Blocked mirror for repositories: [insecure (http://repo.maven.apache.org/maven2/, default, releases+snapshots)]`", + "markdownMessage": "Building your code triggered an access to an insecure HTTP Maven repository. Allow access to insecure repositories, or [update your build to use HTTPS](https://maven.apache.org/docs/3.8.1/release-notes.html#how-to-fix-when-i-get-a-http-repository-blocked).\n\nSuspicious output line: `Caused by: org.eclipse.aether.transfer.ArtifactTransferException: Could not transfer artifact junit-nonesuch:junit-nonesuch:pom:4.11 from/to maven-default-http-blocker (http://0.0.0.0/): Blocked mirror for repositories: [insecure (http://repo.maven.apache.org/maven2/, default, releases+snapshots)]`", "severity": "error", "source": { "extractorName": "java", @@ -27,7 +27,7 @@ } } { - "markdownMessage": "Building your code triggered an access to an insecure HTTP Maven repository. Allow access to insecure repositories, or [update your build to use HTTPS](https://maven.apache.org/docs/3.8.1/release-notes.html#how-to-fix-when-i-get-a-http-repository-blocked). Suspicious output line: `Caused by: org.eclipse.aether.transfer.NoRepositoryConnectorException: Blocked mirror for repositories: [insecure (http://repo.maven.apache.org/maven2/, default, releases+snapshots)]`", + "markdownMessage": "Building your code triggered an access to an insecure HTTP Maven repository. Allow access to insecure repositories, or [update your build to use HTTPS](https://maven.apache.org/docs/3.8.1/release-notes.html#how-to-fix-when-i-get-a-http-repository-blocked).\n\nSuspicious output line: `Caused by: org.eclipse.aether.transfer.NoRepositoryConnectorException: Blocked mirror for repositories: [insecure (http://repo.maven.apache.org/maven2/, default, releases+snapshots)]`", "severity": "error", "source": { "extractorName": "java", @@ -41,7 +41,7 @@ } } { - "markdownMessage": "Building your code triggered an access to an insecure HTTP Maven repository. Allow access to insecure repositories, or [update your build to use HTTPS](https://maven.apache.org/docs/3.8.1/release-notes.html#how-to-fix-when-i-get-a-http-repository-blocked). Suspicious output line: `[ERROR] Failed to execute goal on project maven-sample: Could not resolve dependencies for project com.example:maven-sample:jar:1.0-SNAPSHOT: Failed to collect dependencies at junit-nonesuch:junit-nonesuch:jar:4.11: Failed to read artifact descriptor for junit-nonesuch:junit-nonesuch:jar:4.11: Could not transfer artifact junit-nonesuch:junit-nonesuch:pom:4.11 from/to maven-default-http-blocker (http://0.0.0.0/): Blocked mirror for repositories: [insecure (http://repo.maven.apache.org/maven2/, default, releases+snapshots)] -> [Help 1]`", + "markdownMessage": "Building your code triggered an access to an insecure HTTP Maven repository. Allow access to insecure repositories, or [update your build to use HTTPS](https://maven.apache.org/docs/3.8.1/release-notes.html#how-to-fix-when-i-get-a-http-repository-blocked).\n\nSuspicious output line: `[ERROR] Failed to execute goal on project maven-sample: Could not resolve dependencies for project com.example:maven-sample:jar:1.0-SNAPSHOT: Failed to collect dependencies at junit-nonesuch:junit-nonesuch:jar:4.11: Failed to read artifact descriptor for junit-nonesuch:junit-nonesuch:jar:4.11: Could not transfer artifact junit-nonesuch:junit-nonesuch:pom:4.11 from/to maven-default-http-blocker (http://0.0.0.0/): Blocked mirror for repositories: [insecure (http://repo.maven.apache.org/maven2/, default, releases+snapshots)] -> [Help 1]`", "severity": "error", "source": { "extractorName": "java", diff --git a/java/ql/integration-tests/all-platforms/java/diagnostics/multiple-candidate-builds/diagnostics.expected b/java/ql/integration-tests/all-platforms/java/diagnostics/multiple-candidate-builds/diagnostics.expected index 07c5d942baa..9e7a9ceec33 100644 --- a/java/ql/integration-tests/all-platforms/java/diagnostics/multiple-candidate-builds/diagnostics.expected +++ b/java/ql/integration-tests/all-platforms/java/diagnostics/multiple-candidate-builds/diagnostics.expected @@ -1,5 +1,5 @@ { - "markdownMessage": "Building using Maven was skipped because there were multiple sibling build directories containing build files: [./maven-project-1,./maven-project-2]. If you want to use one of these, please [manually supply a build command](https://docs.github.com/en/github-ae@latest/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages#adding-build-steps-for-a-compiled-language)", + "markdownMessage": "Building using Maven was skipped because there were multiple sibling build directories containing build files: [./maven-project-1,./maven-project-2]. If you want to use one of these, please [manually supply a build command](https://docs.github.com/en/github-ae@latest/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages#adding-build-steps-for-a-compiled-language).", "severity": "warning", "source": { "extractorName": "java", @@ -13,7 +13,7 @@ } } { - "markdownMessage": "If you want to use one of the candidate build systems and directories (see previous warnings), please [supply a manual a build command](https://docs.github.com/en/github-ae@latest/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages#adding-build-steps-for-a-compiled-language)", + "markdownMessage": "If you want to use one of the candidate build systems and directories (see previous warnings), please [supply a manual a build command](https://docs.github.com/en/github-ae@latest/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages#adding-build-steps-for-a-compiled-language).", "severity": "error", "source": { "extractorName": "java", diff --git a/java/ql/integration-tests/all-platforms/java/diagnostics/no-build-system/diagnostics.expected b/java/ql/integration-tests/all-platforms/java/diagnostics/no-build-system/diagnostics.expected index e7a8e2ed10d..f465cd5df84 100644 --- a/java/ql/integration-tests/all-platforms/java/diagnostics/no-build-system/diagnostics.expected +++ b/java/ql/integration-tests/all-platforms/java/diagnostics/no-build-system/diagnostics.expected @@ -1,5 +1,5 @@ { - "markdownMessage": "Could not find a Gradle, Maven or Ant top-level project to build. Please [supply a manual build command](https://docs.github.com/en/github-ae@latest/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages#adding-build-steps-for-a-compiled-language)", + "markdownMessage": "Could not find a Gradle, Maven or Ant top-level project to build. Please [supply a manual build command](https://docs.github.com/en/github-ae@latest/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages#adding-build-steps-for-a-compiled-language).", "severity": "error", "source": { "extractorName": "java", diff --git a/java/ql/integration-tests/all-platforms/java/diagnostics/no-gradle-test-classes/diagnostics.expected b/java/ql/integration-tests/all-platforms/java/diagnostics/no-gradle-test-classes/diagnostics.expected index c5d896da5be..a6c5e2eb877 100644 --- a/java/ql/integration-tests/all-platforms/java/diagnostics/no-gradle-test-classes/diagnostics.expected +++ b/java/ql/integration-tests/all-platforms/java/diagnostics/no-gradle-test-classes/diagnostics.expected @@ -13,7 +13,7 @@ } } { - "markdownMessage": "Gradle project does not define a `testClasses` goal. [Supply a manual build command](https://docs.github.com/en/github-ae@latest/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages#adding-build-steps-for-a-compiled-language) that builds the code that should be analyzed. Suspicious output line: `org.gradle.execution.TaskSelectionException: Task 'testClasses' not found in root project 'no-gradle-test-classes'.`", + "markdownMessage": "Gradle project does not define a `testClasses` goal. [Supply a manual build command](https://docs.github.com/en/github-ae@latest/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages#adding-build-steps-for-a-compiled-language) that builds the code that should be analyzed.\n\nSuspicious output line: `org.gradle.execution.TaskSelectionException: Task 'testClasses' not found in root project 'no-gradle-test-classes'.`", "severity": "error", "source": { "extractorName": "java", diff --git a/java/ql/integration-tests/all-platforms/kotlin/diagnostics/kotlin-version-too-new/diagnostics.expected b/java/ql/integration-tests/all-platforms/kotlin/diagnostics/kotlin-version-too-new/diagnostics.expected index 3494b6a6f7d..3397ea1bdef 100644 --- a/java/ql/integration-tests/all-platforms/kotlin/diagnostics/kotlin-version-too-new/diagnostics.expected +++ b/java/ql/integration-tests/all-platforms/kotlin/diagnostics/kotlin-version-too-new/diagnostics.expected @@ -1,10 +1,10 @@ { - "markdownMessage": "The Kotlin version installed (`999.999.999`) is too recent for this version of CodeQL. Install a version lower than 1.8.30", + "markdownMessage": "The Kotlin version installed (`999.999.999`) is too recent for this version of CodeQL. Install a version lower than 1.8.30.", "severity": "error", "source": { "extractorName": "java", "id": "java/extractor-agent/kotlin-version-too-new", - "name": "Android build failure" + "name": "Kotlin version too new" }, "visibility": { "cliSummaryTable": true, diff --git a/java/ql/lib/change-notes/2023-02-17-add-hardcoded-secret-for-jwt-tokens.md b/java/ql/lib/change-notes/2023-02-17-add-hardcoded-secret-for-jwt-tokens.md new file mode 100644 index 00000000000..408bb13755b --- /dev/null +++ b/java/ql/lib/change-notes/2023-02-17-add-hardcoded-secret-for-jwt-tokens.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Added new sinks for `java/hardcoded-credential-api-call` to identify the use of hardcoded secrets in the creation and verification of JWT tokens using `com.auth0.jwt`. These sinks are from [an experimental query submitted by @luchua](https://github.com/github/codeql/pull/9036). diff --git a/java/ql/lib/change-notes/2023-03-02-dataflow-conf-module.md b/java/ql/lib/change-notes/2023-03-02-dataflow-conf-module.md new file mode 100644 index 00000000000..89190af399f --- /dev/null +++ b/java/ql/lib/change-notes/2023-03-02-dataflow-conf-module.md @@ -0,0 +1,9 @@ +--- +category: majorAnalysis +--- +* The main data flow and taint tracking APIs have been changed. The old APIs + remain in place for now and translate to the new through a + backwards-compatible wrapper. If multiple configurations are in scope + simultaneously, then this may affect results slightly. The new API is quite + similar to the old, but makes use of a configuration module instead of a + configuration class. diff --git a/java/ql/lib/ext/java.lang.model.yml b/java/ql/lib/ext/java.lang.model.yml index 0f25c8d0cf5..55a7f7f8990 100644 --- a/java/ql/lib/ext/java.lang.model.yml +++ b/java/ql/lib/ext/java.lang.model.yml @@ -3,6 +3,8 @@ extensions: pack: codeql/java-all extensible: sinkModel data: + - ["java.lang", "Module", True, "getResourceAsStream", "(String)", "", "Argument[0]", "read-file", "ai-generated"] +# suggested label is not supported: - ["java.lang", "ProcessBuilder", True, "ProcessBuilder", "(String[])", "", "Argument[0]", "command-injection", "ai-generated"] - ["java.lang", "String", False, "matches", "(String)", "", "Argument[0]", "regex-use[f-1]", "manual"] - ["java.lang", "String", False, "replaceAll", "(String,String)", "", "Argument[0]", "regex-use[-1]", "manual"] - ["java.lang", "String", False, "replaceFirst", "(String,String)", "", "Argument[0]", "regex-use[-1]", "manual"] diff --git a/java/ql/lib/ext/org.apache.hadoop.hive.metastore.api.model.yml b/java/ql/lib/ext/org.apache.hadoop.hive.metastore.api.model.yml new file mode 100644 index 00000000000..3d6e5c3e5ee --- /dev/null +++ b/java/ql/lib/ext/org.apache.hadoop.hive.metastore.api.model.yml @@ -0,0 +1,7 @@ +extensions: + - addsTo: + pack: codeql/java-all + extensible: sinkModel + data: + - ["org.apache.hadoop.hive.metastore.api", "DefaultConstraintsRequest", True, "DefaultConstraintsRequest", "(String,String,String)", "", "Argument[1]", "sql", "ai-generated"] + diff --git a/java/ql/lib/ext/org.apache.hadoop.hive.metastore.model.yml b/java/ql/lib/ext/org.apache.hadoop.hive.metastore.model.yml new file mode 100644 index 00000000000..607b70e6658 --- /dev/null +++ b/java/ql/lib/ext/org.apache.hadoop.hive.metastore.model.yml @@ -0,0 +1,8 @@ +extensions: + - addsTo: + pack: codeql/java-all + extensible: sinkModel + data: + - ["org.apache.hadoop.hive.metastore", "ObjectStore", True, "updatePartitionColumnStatistics", "(ColumnStatistics,List,String,long)", "", "Argument[0]", "sql", "ai-generated"] + - ["org.apache.hadoop.hive.metastore", "ObjectStore", True, "updatePartitionColumnStatistics", "(ColumnStatistics,List)", "", "Argument[0]", "sql", "ai-generated"] + diff --git a/java/ql/lib/ext/org.apache.hive.hcatalog.templeton.model.yml b/java/ql/lib/ext/org.apache.hive.hcatalog.templeton.model.yml new file mode 100644 index 00000000000..8b6509cdb4a --- /dev/null +++ b/java/ql/lib/ext/org.apache.hive.hcatalog.templeton.model.yml @@ -0,0 +1,7 @@ +extensions: + - addsTo: + pack: codeql/java-all + extensible: sinkModel + data: + - ["org.apache.hive.hcatalog.templeton", "HcatDelegator", True, "addOneColumn", "(String,String,String,ColumnDesc)", "", "Argument[3]", "sql", "ai-generated"] + diff --git a/java/ql/lib/semmle/code/java/dataflow/DataFlow.qll b/java/ql/lib/semmle/code/java/dataflow/DataFlow.qll index 1b4409234e6..3676423ccbf 100644 --- a/java/ql/lib/semmle/code/java/dataflow/DataFlow.qll +++ b/java/ql/lib/semmle/code/java/dataflow/DataFlow.qll @@ -6,5 +6,6 @@ import java module DataFlow { - import semmle.code.java.dataflow.internal.DataFlowImpl + import semmle.code.java.dataflow.internal.DataFlow + import semmle.code.java.dataflow.internal.DataFlowImpl1 } diff --git a/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll b/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll index 5bcd07be9b0..31a321da0ee 100644 --- a/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll +++ b/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll @@ -67,9 +67,10 @@ * "taint" indicates a default additional taint step and "value" indicates a * globally applicable value-preserving step. * 9. The `provenance` column is a tag to indicate the origin of the summary. - * There are two supported values: "generated" and "manual". "generated" means that - * the model has been emitted by the model generator tool and "manual" means - * that the model has been written by hand. + * The supported values are: "manual", "generated" and "ai-generated". "manual" + * means that the model has been written by hand, "generated" means that + * the model has been emitted by the model generator tool and + * "ai-generated" means that the model has been AI generated (ATM project). */ import java @@ -308,7 +309,7 @@ module ModelValidation { not ext.regexpMatch("|Annotated") and result = "Unrecognized extra API graph element \"" + ext + "\" in " + pred + " model." or - not provenance = ["manual", "generated"] and + not provenance = ["manual", "generated", "ai-generated"] and result = "Unrecognized provenance description \"" + provenance + "\" in " + pred + " model." ) } diff --git a/java/ql/lib/semmle/code/java/dataflow/TaintTracking.qll b/java/ql/lib/semmle/code/java/dataflow/TaintTracking.qll index 853b8c5d63c..37a26bf38bf 100644 --- a/java/ql/lib/semmle/code/java/dataflow/TaintTracking.qll +++ b/java/ql/lib/semmle/code/java/dataflow/TaintTracking.qll @@ -8,5 +8,6 @@ import semmle.code.java.dataflow.DataFlow2 import semmle.code.java.dataflow.internal.TaintTrackingUtil::StringBuilderVarModule module TaintTracking { + import semmle.code.java.dataflow.internal.tainttracking1.TaintTracking import semmle.code.java.dataflow.internal.tainttracking1.TaintTrackingImpl } diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlow.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlow.qll new file mode 100644 index 00000000000..fe9b9b18941 --- /dev/null +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlow.qll @@ -0,0 +1,245 @@ +/** + * Provides an implementation of global (interprocedural) data flow. This file + * re-exports the local (intraprocedural) data flow analysis from + * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed + * through the `Make` and `MakeWithState` modules. + */ + +private import DataFlowImplCommon +private import DataFlowImplSpecific::Private +import DataFlowImplSpecific::Public +import DataFlowImplCommonPublic +private import DataFlowImpl + +/** An input configuration for data flow. */ +signature module ConfigSig { + /** + * Holds if `source` is a relevant data flow source. + */ + predicate isSource(Node source); + + /** + * Holds if `sink` is a relevant data flow sink. + */ + predicate isSink(Node sink); + + /** + * Holds if data flow through `node` is prohibited. This completely removes + * `node` from the data flow graph. + */ + default predicate isBarrier(Node node) { none() } + + /** Holds if data flow into `node` is prohibited. */ + default predicate isBarrierIn(Node node) { none() } + + /** Holds if data flow out of `node` is prohibited. */ + default predicate isBarrierOut(Node node) { none() } + + /** + * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. + */ + default predicate isAdditionalFlowStep(Node node1, Node node2) { none() } + + /** + * Holds if an arbitrary number of implicit read steps of content `c` may be + * taken at `node`. + */ + default predicate allowImplicitRead(Node node, ContentSet c) { none() } + + /** + * Gets the virtual dispatch branching limit when calculating field flow. + * This can be overridden to a smaller value to improve performance (a + * value of 0 disables field flow), or a larger value to get more results. + */ + default int fieldFlowBranchLimit() { result = 2 } + + /** + * Gets a data flow configuration feature to add restrictions to the set of + * valid flow paths. + * + * - `FeatureHasSourceCallContext`: + * Assume that sources have some existing call context to disallow + * conflicting return-flow directly following the source. + * - `FeatureHasSinkCallContext`: + * Assume that sinks have some existing call context to disallow + * conflicting argument-to-parameter flow directly preceding the sink. + * - `FeatureEqualSourceSinkCallContext`: + * Implies both of the above and additionally ensures that the entire flow + * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. + */ + default FlowFeature getAFeature() { none() } + + /** Holds if sources should be grouped in the result of `hasFlowPath`. */ + default predicate sourceGrouping(Node source, string sourceGroup) { none() } + + /** Holds if sinks should be grouped in the result of `hasFlowPath`. */ + default predicate sinkGrouping(Node sink, string sinkGroup) { none() } + + /** + * Holds if hidden nodes should be included in the data flow graph. + * + * This feature should only be used for debugging or when the data flow graph + * is not visualized (as it is in a `path-problem` query). + */ + default predicate includeHiddenNodes() { none() } +} + +/** An input configuration for data flow using flow state. */ +signature module StateConfigSig { + bindingset[this] + class FlowState; + + /** + * Holds if `source` is a relevant data flow source with the given initial + * `state`. + */ + predicate isSource(Node source, FlowState state); + + /** + * Holds if `sink` is a relevant data flow sink accepting `state`. + */ + predicate isSink(Node sink, FlowState state); + + /** + * Holds if data flow through `node` is prohibited. This completely removes + * `node` from the data flow graph. + */ + default predicate isBarrier(Node node) { none() } + + /** + * Holds if data flow through `node` is prohibited when the flow state is + * `state`. + */ + predicate isBarrier(Node node, FlowState state); + + /** Holds if data flow into `node` is prohibited. */ + default predicate isBarrierIn(Node node) { none() } + + /** Holds if data flow out of `node` is prohibited. */ + default predicate isBarrierOut(Node node) { none() } + + /** + * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. + */ + default predicate isAdditionalFlowStep(Node node1, Node node2) { none() } + + /** + * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. + * This step is only applicable in `state1` and updates the flow state to `state2`. + */ + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2); + + /** + * Holds if an arbitrary number of implicit read steps of content `c` may be + * taken at `node`. + */ + default predicate allowImplicitRead(Node node, ContentSet c) { none() } + + /** + * Gets the virtual dispatch branching limit when calculating field flow. + * This can be overridden to a smaller value to improve performance (a + * value of 0 disables field flow), or a larger value to get more results. + */ + default int fieldFlowBranchLimit() { result = 2 } + + /** + * Gets a data flow configuration feature to add restrictions to the set of + * valid flow paths. + * + * - `FeatureHasSourceCallContext`: + * Assume that sources have some existing call context to disallow + * conflicting return-flow directly following the source. + * - `FeatureHasSinkCallContext`: + * Assume that sinks have some existing call context to disallow + * conflicting argument-to-parameter flow directly preceding the sink. + * - `FeatureEqualSourceSinkCallContext`: + * Implies both of the above and additionally ensures that the entire flow + * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. + */ + default FlowFeature getAFeature() { none() } + + /** Holds if sources should be grouped in the result of `hasFlowPath`. */ + default predicate sourceGrouping(Node source, string sourceGroup) { none() } + + /** Holds if sinks should be grouped in the result of `hasFlowPath`. */ + default predicate sinkGrouping(Node sink, string sinkGroup) { none() } + + /** + * Holds if hidden nodes should be included in the data flow graph. + * + * This feature should only be used for debugging or when the data flow graph + * is not visualized (as it is in a `path-problem` query). + */ + default predicate includeHiddenNodes() { none() } +} + +/** + * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` + * measured in approximate number of interprocedural steps. + */ +signature int explorationLimitSig(); + +/** + * The output of a data flow computation. + */ +signature module DataFlowSig { + /** + * A `Node` augmented with a call context (except for sinks) and an access path. + * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. + */ + class PathNode; + + /** + * Holds if data can flow from `source` to `sink`. + * + * The corresponding paths are generated from the end-points and the graph + * included in the module `PathGraph`. + */ + predicate hasFlowPath(PathNode source, PathNode sink); + + /** + * Holds if data can flow from `source` to `sink`. + */ + predicate hasFlow(Node source, Node sink); + + /** + * Holds if data can flow from some source to `sink`. + */ + predicate hasFlowTo(Node sink); + + /** + * Holds if data can flow from some source to `sink`. + */ + predicate hasFlowToExpr(DataFlowExpr sink); +} + +/** + * Constructs a standard data flow computation. + */ +module Make implements DataFlowSig { + private module C implements FullStateConfigSig { + import DefaultState + import Config + } + + import Impl +} + +/** + * Constructs a data flow computation using flow state. + */ +module MakeWithState implements DataFlowSig { + private module C implements FullStateConfigSig { + import Config + } + + import Impl +} diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 1b969756b09..0afcba55582 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -1,135 +1,75 @@ /** - * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from - * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed - * through the `Configuration` class. This file exists in several identical - * copies, allowing queries to use multiple `Configuration` classes that depend - * on each other without introducing mutual recursion among those configurations. + * INTERNAL: Do not use. + * + * Provides an implementation of global (interprocedural) data flow. */ private import DataFlowImplCommon private import DataFlowImplSpecific::Private -import DataFlowImplSpecific::Public -import DataFlowImplCommonPublic +private import DataFlowImplSpecific::Public +private import DataFlowImplCommonPublic +import DataFlow /** - * A configuration of interprocedural data flow analysis. This defines - * sources, sinks, and any other configurable aspect of the analysis. Each - * use of the global data flow library must define its own unique extension - * of this abstract class. To create a configuration, extend this class with - * a subclass whose characteristic predicate is a unique singleton string. - * For example, write - * - * ```ql - * class MyAnalysisConfiguration extends DataFlow::Configuration { - * MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" } - * // Override `isSource` and `isSink`. - * // Optionally override `isBarrier`. - * // Optionally override `isAdditionalFlowStep`. - * } - * ``` - * Conceptually, this defines a graph where the nodes are `DataFlow::Node`s and - * the edges are those data-flow steps that preserve the value of the node - * along with any additional edges defined by `isAdditionalFlowStep`. - * Specifying nodes in `isBarrier` will remove those nodes from the graph, and - * specifying nodes in `isBarrierIn` and/or `isBarrierOut` will remove in-going - * and/or out-going edges from those nodes, respectively. - * - * Then, to query whether there is flow between some `source` and `sink`, - * write - * - * ```ql - * exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink)) - * ``` - * - * Multiple configurations can coexist, but two classes extending - * `DataFlow::Configuration` should never depend on each other. One of them - * should instead depend on a `DataFlow2::Configuration`, a - * `DataFlow3::Configuration`, or a `DataFlow4::Configuration`. + * An input configuration for data flow using flow state. This signature equals + * `StateConfigSig`, but requires explicit implementation of all predicates. */ -abstract class Configuration extends string { +signature module FullStateConfigSig { bindingset[this] - Configuration() { any() } - - /** - * Holds if `source` is a relevant data flow source. - */ - predicate isSource(Node source) { none() } + class FlowState; /** * Holds if `source` is a relevant data flow source with the given initial * `state`. */ - predicate isSource(Node source, FlowState state) { none() } - - /** - * Holds if `sink` is a relevant data flow sink. - */ - predicate isSink(Node sink) { none() } + predicate isSource(Node source, FlowState state); /** * Holds if `sink` is a relevant data flow sink accepting `state`. */ - predicate isSink(Node sink, FlowState state) { none() } + predicate isSink(Node sink, FlowState state); /** * Holds if data flow through `node` is prohibited. This completely removes * `node` from the data flow graph. */ - predicate isBarrier(Node node) { none() } + predicate isBarrier(Node node); /** * Holds if data flow through `node` is prohibited when the flow state is * `state`. */ - predicate isBarrier(Node node, FlowState state) { none() } + predicate isBarrier(Node node, FlowState state); /** Holds if data flow into `node` is prohibited. */ - predicate isBarrierIn(Node node) { none() } + predicate isBarrierIn(Node node); /** Holds if data flow out of `node` is prohibited. */ - predicate isBarrierOut(Node node) { none() } - - /** - * DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead. - * - * Holds if data flow through nodes guarded by `guard` is prohibited. - */ - deprecated predicate isBarrierGuard(BarrierGuard guard) { none() } - - /** - * DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead. - * - * Holds if data flow through nodes guarded by `guard` is prohibited when - * the flow state is `state` - */ - deprecated predicate isBarrierGuard(BarrierGuard guard, FlowState state) { none() } + predicate isBarrierOut(Node node); /** * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. */ - predicate isAdditionalFlowStep(Node node1, Node node2) { none() } + predicate isAdditionalFlowStep(Node node1, Node node2); /** * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. * This step is only applicable in `state1` and updates the flow state to `state2`. */ - predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { - none() - } + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2); /** * Holds if an arbitrary number of implicit read steps of content `c` may be * taken at `node`. */ - predicate allowImplicitRead(Node node, ContentSet c) { none() } + predicate allowImplicitRead(Node node, ContentSet c); /** * Gets the virtual dispatch branching limit when calculating field flow. * This can be overridden to a smaller value to improve performance (a * value of 0 disables field flow), or a larger value to get more results. */ - int fieldFlowBranchLimit() { result = 2 } + int fieldFlowBranchLimit(); /** * Gets a data flow configuration feature to add restrictions to the set of @@ -144,1720 +84,680 @@ abstract class Configuration extends string { * - `FeatureEqualSourceSinkCallContext`: * Implies both of the above and additionally ensures that the entire flow * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. */ - FlowFeature getAFeature() { none() } + FlowFeature getAFeature(); /** Holds if sources should be grouped in the result of `hasFlowPath`. */ - predicate sourceGrouping(Node source, string sourceGroup) { none() } + predicate sourceGrouping(Node source, string sourceGroup); /** Holds if sinks should be grouped in the result of `hasFlowPath`. */ - predicate sinkGrouping(Node sink, string sinkGroup) { none() } - - /** - * Holds if data may flow from `source` to `sink` for this configuration. - */ - predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } - - /** - * Holds if data may flow from `source` to `sink` for this configuration. - * - * The corresponding paths are generated from the end-points and the graph - * included in the module `PathGraph`. - */ - predicate hasFlowPath(PathNode source, PathNode sink) { hasFlowPath(source, sink, this) } - - /** - * Holds if data may flow from some source to `sink` for this configuration. - */ - predicate hasFlowTo(Node sink) { - sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode() - } - - /** - * Holds if data may flow from some source to `sink` for this configuration. - */ - predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } - - /** - * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` - * measured in approximate number of interprocedural steps. - */ - int explorationLimit() { none() } + predicate sinkGrouping(Node sink, string sinkGroup); /** * Holds if hidden nodes should be included in the data flow graph. * * This feature should only be used for debugging or when the data flow graph - * is not visualized (for example in a `path-problem` query). + * is not visualized (as it is in a `path-problem` query). */ - predicate includeHiddenNodes() { none() } + predicate includeHiddenNodes(); +} - /** - * Holds if there is a partial data flow path from `source` to `node`. The - * approximate distance between `node` and the closest source is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards sink definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sources is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - */ - final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { - partialFlow(source, node, this) and - dist = node.getSourceDistance() - } +/** + * Provides default `FlowState` implementations given a `StateConfigSig`. + */ +module DefaultState { + class FlowState = Unit; - /** - * Holds if there is a partial data flow path from `node` to `sink`. The - * approximate distance between `node` and the closest sink is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards source definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sinks is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - * - * Note that reverse flow has slightly lower precision than the corresponding - * forward flow, as reverse flow disregards type pruning among other features. - */ - final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { - revPartialFlow(node, sink, this) and - dist = node.getSinkDistance() + predicate isSource(Node source, FlowState state) { Config::isSource(source) and exists(state) } + + predicate isSink(Node sink, FlowState state) { Config::isSink(sink) and exists(state) } + + predicate isBarrier(Node node, FlowState state) { none() } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + none() } } /** - * This class exists to prevent mutual recursion between the user-overridden - * member predicates of `Configuration` and the rest of the data-flow library. - * Good performance cannot be guaranteed in the presence of such recursion, so - * it should be replaced by using more than one copy of the data flow library. + * Constructs a data flow computation given a full input configuration. */ -abstract private class ConfigurationRecursionPrevention extends Configuration { - bindingset[this] - ConfigurationRecursionPrevention() { any() } +module Impl { + private class FlowState = Config::FlowState; - override predicate hasFlow(Node source, Node sink) { - strictcount(Node n | this.isSource(n)) < 0 - or - strictcount(Node n | this.isSource(n, _)) < 0 - or - strictcount(Node n | this.isSink(n)) < 0 - or - strictcount(Node n | this.isSink(n, _)) < 0 - or - strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0 - or - strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, _, n2, _)) < 0 - or - super.hasFlow(source, sink) - } -} + private newtype TNodeEx = + TNodeNormal(Node n) or + TNodeImplicitRead(Node n, boolean hasRead) { + Config::allowImplicitRead(n, _) and hasRead = [false, true] + } -private newtype TNodeEx = - TNodeNormal(Node n) or - TNodeImplicitRead(Node n, boolean hasRead) { - any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true] - } - -private class NodeEx extends TNodeEx { - string toString() { - result = this.asNode().toString() - or - exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") - } - - Node asNode() { this = TNodeNormal(result) } - - predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } - - Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } - - pragma[nomagic] - private DataFlowCallable getEnclosingCallable0() { - nodeEnclosingCallable(this.projectToNode(), result) - } - - pragma[inline] - DataFlowCallable getEnclosingCallable() { - pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) - } - - pragma[nomagic] - private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } - - pragma[inline] - DataFlowType getDataFlowType() { - pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) - } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -private class ArgNodeEx extends NodeEx { - ArgNodeEx() { this.asNode() instanceof ArgNode } -} - -private class ParamNodeEx extends NodeEx { - ParamNodeEx() { this.asNode() instanceof ParamNode } - - predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { - this.asNode().(ParamNode).isParameterOf(c, pos) - } - - ParameterPosition getPosition() { this.isParameterOf(_, result) } -} - -private class RetNodeEx extends NodeEx { - RetNodeEx() { this.asNode() instanceof ReturnNodeExt } - - ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } - - ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } -} - -private predicate inBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierIn(n) - | - config.isSource(n) or config.isSource(n, _) - ) -} - -private predicate outBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierOut(n) - | - config.isSink(n) or config.isSink(n, _) - ) -} - -/** A bridge class to access the deprecated `isBarrierGuard`. */ -private class BarrierGuardGuardedNodeBridge extends Unit { - abstract predicate guardedNode(Node n, Configuration config); - - abstract predicate guardedNode(Node n, FlowState state, Configuration config); -} - -private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { - deprecated override predicate guardedNode(Node n, Configuration config) { - exists(BarrierGuard g | - config.isBarrierGuard(g) and - n = g.getAGuardedNode() - ) - } - - deprecated override predicate guardedNode(Node n, FlowState state, Configuration config) { - exists(BarrierGuard g | - config.isBarrierGuard(g, state) and - n = g.getAGuardedNode() - ) - } -} - -pragma[nomagic] -private predicate fullBarrier(NodeEx node, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n) - or - config.isBarrierIn(n) and - not config.isSource(n) and - not config.isSource(n, _) - or - config.isBarrierOut(n) and - not config.isSink(n) and - not config.isSink(n, _) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config) - ) -} - -pragma[nomagic] -private predicate stateBarrier(NodeEx node, FlowState state, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n, state) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config) - ) -} - -pragma[nomagic] -private predicate sourceNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSource(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSource(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -pragma[nomagic] -private predicate sinkNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSink(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSink(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -/** Provides the relevant barriers for a step from `node1` to `node2`. */ -pragma[inline] -private predicate stepFilter(NodeEx node1, NodeEx node2, Configuration config) { - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) -} - -/** - * Holds if data can flow in one local step from `node1` to `node2`. - */ -private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.asNode() = n and - node2.isImplicitReadNode(n, false) and - not fullBarrier(node1, config) - ) -} - -/** - * Holds if the additional step from `node1` to `node2` does not jump between callables. - */ -private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.isImplicitReadNode(n, true) and - node2.asNode() = n and - not fullBarrier(node2, config) - ) -} - -private predicate additionalLocalStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) - ) -} - -/** - * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. - */ -private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if the additional step from `node1` to `node2` jumps between callables. - */ -private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate additionalJumpStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -pragma[nomagic] -private predicate readSet(NodeEx node1, ContentSet c, NodeEx node2, Configuration config) { - readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and - stepFilter(node1, node2, config) - or - exists(Node n | - node2.isImplicitReadNode(n, true) and - node1.isImplicitReadNode(n, _) and - config.allowImplicitRead(n, c) - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(ContentSet cs | - readSet(node1, cs, node2, config) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate clearsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - clearsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate expectsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - expectsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -pragma[nomagic] -private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } - -pragma[nomagic] -private predicate hasReadStep(Content c, Configuration config) { read(_, c, _, config) } - -pragma[nomagic] -private predicate store( - NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config -) { - store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), - contentType) and - hasReadStep(tc.getContent(), config) and - stepFilter(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { - viableReturnPosOut(call, pos, out.asNode()) -} - -pragma[nomagic] -private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { - viableParamArg(call, p.asNode(), arg.asNode()) -} - -/** - * Holds if field flow should be used for the given configuration. - */ -private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } - -private predicate hasSourceCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSourceCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate hasSinkCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSinkCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if flow from `p` to a return node of kind `kind` is allowed. - * - * We don't expect a parameter to return stored in itself, unless - * explicitly allowed - */ -bindingset[p, kind] -private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { - exists(ParameterPosition pos | p.isParameterOf(_, pos) | - not kind.(ParamUpdateReturnKind).getPosition() = pos - or - allowParameterReturnInSelfCached(p.asNode()) - ) -} - -private module Stage1 implements StageSig { - class Ap extends int { - // workaround for bad functionality-induced joins (happens when using `Unit`) - pragma[nomagic] - Ap() { this in [0 .. 1] and this < 1 } - } - - private class Cc = boolean; - - /* Begin: Stage 1 logic. */ - /** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `cc` records whether the node is reached through an - * argument in a call. - */ - private predicate fwdFlow(NodeEx node, Cc cc, Configuration config) { - sourceNode(node, _, config) and - if hasSourceCallCtx(config) then cc = true else cc = false - or - exists(NodeEx mid | fwdFlow(mid, cc, config) | - localFlowStep(mid, node, config) or - additionalLocalFlowStep(mid, node, config) or - additionalLocalStateStep(mid, _, node, _, config) - ) - or - exists(NodeEx mid | fwdFlow(mid, _, config) and cc = false | - jumpStep(mid, node, config) or - additionalJumpStep(mid, node, config) or - additionalJumpStateStep(mid, _, node, _, config) - ) - or - // store - exists(NodeEx mid | - useFieldFlow(config) and - fwdFlow(mid, cc, config) and - store(mid, _, node, _, config) - ) - or - // read - exists(ContentSet c | - fwdFlowReadSet(c, node, cc, config) and - fwdFlowConsCandSet(c, _, config) - ) - or - // flow into a callable - fwdFlowIn(_, _, _, node, config) and - cc = true - or - // flow out of a callable - fwdFlowOut(_, node, false, config) and - cc = false - or - // flow through a callable - exists(DataFlowCall call | - fwdFlowOutFromArg(call, node, config) and - fwdFlowIsEntered(call, cc, config) - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowIn( - DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p, Configuration config - ) { - // call context cannot help reduce virtual dispatch - fwdFlow(arg, cc, config) and - viableParamArgEx(call, p, arg) and - not fullBarrier(p, config) and - ( - cc = false + private class NodeEx extends TNodeEx { + string toString() { + result = this.asNode().toString() or - cc = true and - not reducedViableImplInCallContext(call, _, _) - ) - or - // call context may help reduce virtual dispatch - exists(DataFlowCallable target | - fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target, config) and - target = viableImplInSomeFwdFlowCallContextExt(call, config) and - cc = true - ) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - fwdFlowIn(call, _, cc, _, config) - } - - pragma[nomagic] - private predicate fwdFlowInReducedViableImplInSomeCallContext( - DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target, Configuration config - ) { - fwdFlow(arg, true, config) and - viableParamArgEx(call, p, arg) and - reducedViableImplInCallContext(call, _, _) and - target = p.getEnclosingCallable() and - not fullBarrier(p, config) - } - - /** - * Gets a viable dispatch target of `call` in the context `ctx`. This is - * restricted to those `call`s for which a context might make a difference, - * and to `ctx`s that are reachable in `fwdFlow`. - */ - pragma[nomagic] - private DataFlowCallable viableImplInSomeFwdFlowCallContextExt( - DataFlowCall call, Configuration config - ) { - exists(DataFlowCall ctx | - fwdFlowIsEntered(ctx, _, config) and - result = viableImplInCallContextExt(call, ctx) - ) - } - - private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) } - - pragma[nomagic] - private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc, Configuration config) { - exists(NodeEx mid | - fwdFlow(mid, cc, config) and - readSet(mid, c, node, config) - ) - } - - /** - * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - fwdFlow(mid, _, config) and - store(mid, tc, node, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `cs` may be interpreted in a read as the target of some store - * into `c`, in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCandSet(ContentSet cs, Content c, Configuration config) { - fwdFlowConsCand(c, config) and - c = cs.getAReadContent() - } - - pragma[nomagic] - private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { - exists(RetNodeEx ret | - fwdFlow(ret, cc, config) and - ret.getReturnPosition() = pos - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) { - exists(ReturnPosition pos | - fwdFlowReturnPosition(pos, cc, config) and - viableReturnPosOutEx(call, pos, out) and - not fullBarrier(out, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) { - fwdFlowOut(call, out, true, config) - } - - private predicate stateStepFwd(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1 | - additionalLocalStateStep(node1, state1, _, state2, config) or - additionalJumpStateStep(node1, state1, _, state2, config) - | - fwdFlow(node1, config) - ) - } - - private predicate fwdFlowState(FlowState state, Configuration config) { - sourceNode(_, state, config) - or - exists(FlowState state0 | - fwdFlowState(state0, config) and - stateStepFwd(state0, state, config) - ) - } - - /** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink. - */ - pragma[nomagic] - private predicate revFlow(NodeEx node, boolean toReturn, Configuration config) { - revFlow0(node, toReturn, config) and - fwdFlow(node, config) - } - - pragma[nomagic] - private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) { - exists(FlowState state | - fwdFlow(node, pragma[only_bind_into](config)) and - sinkNode(node, state, config) and - fwdFlowState(state, pragma[only_bind_into](config)) and - if hasSinkCallCtx(config) then toReturn = true else toReturn = false - ) - or - exists(NodeEx mid | revFlow(mid, toReturn, config) | - localFlowStep(node, mid, config) or - additionalLocalFlowStep(node, mid, config) or - additionalLocalStateStep(node, _, mid, _, config) - ) - or - exists(NodeEx mid | revFlow(mid, _, config) and toReturn = false | - jumpStep(node, mid, config) or - additionalJumpStep(node, mid, config) or - additionalJumpStateStep(node, _, mid, _, config) - ) - or - // store - exists(Content c | - revFlowStore(c, node, toReturn, config) and - revFlowConsCand(c, config) - ) - or - // read - exists(NodeEx mid, ContentSet c | - readSet(node, c, mid, config) and - fwdFlowConsCandSet(c, _, pragma[only_bind_into](config)) and - revFlow(mid, toReturn, pragma[only_bind_into](config)) - ) - or - // flow into a callable - revFlowIn(_, node, false, config) and - toReturn = false - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(pos, config) and - node.(RetNodeEx).getReturnPosition() = pos and - toReturn = true - ) - or - // flow through a callable - exists(DataFlowCall call | - revFlowInToReturn(call, node, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - /** - * Holds if `c` is the target of a read in the flow covered by `revFlow`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, ContentSet cs | - fwdFlow(node, pragma[only_bind_into](config)) and - readSet(node, cs, mid, config) and - fwdFlowConsCandSet(cs, c, pragma[only_bind_into](config)) and - revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) { - exists(NodeEx mid, TypedContent tc | - revFlow(mid, toReturn, pragma[only_bind_into](config)) and - fwdFlowConsCand(c, pragma[only_bind_into](config)) and - store(node, tc, mid, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `c` is the target of both a read and a store in the flow covered - * by `revFlow`. - */ - pragma[nomagic] - additional predicate revFlowIsReadAndStored(Content c, Configuration conf) { - revFlowConsCand(c, conf) and - revFlowStore(c, _, _, conf) - } - - pragma[nomagic] - additional predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config - ) { - fwdFlowReturnPosition(pos, _, config) and - viableReturnPosOutEx(call, pos, out) - } - - pragma[nomagic] - private predicate revFlowOut(ReturnPosition pos, Configuration config) { - exists(NodeEx out | - revFlow(out, _, config) and - viableReturnPosOutNodeCandFwd1(_, pos, out, config) - ) - } - - pragma[nomagic] - additional predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config - ) { - fwdFlowIn(call, arg, _, p, config) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate revFlowIn( - DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config - ) { - exists(ParamNodeEx p | - revFlow(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) { - revFlowIn(call, arg, true, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(NodeEx out | - revFlow(out, toReturn, config) and - fwdFlowOutFromArg(call, out, config) - ) - } - - private predicate stateStepRev(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - additionalLocalStateStep(node1, state1, node2, state2, config) or - additionalJumpStateStep(node1, state1, node2, state2, config) - | - revFlow(node1, _, pragma[only_bind_into](config)) and - revFlow(node2, _, pragma[only_bind_into](config)) and - fwdFlowState(state1, pragma[only_bind_into](config)) and - fwdFlowState(state2, pragma[only_bind_into](config)) - ) - } - - additional predicate revFlowState(FlowState state, Configuration config) { - exists(NodeEx node | - sinkNode(node, state, config) and - revFlow(node, _, pragma[only_bind_into](config)) and - fwdFlowState(state, pragma[only_bind_into](config)) - ) - or - exists(FlowState state0 | - revFlowState(state0, config) and - stateStepRev(state, state0, config) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Content c | - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - revFlow(node2, pragma[only_bind_into](config)) and - store(node1, tc, node2, contentType, config) and - c = tc.getContent() and - exists(ap1) - ) - } - - pragma[nomagic] - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) { - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - read(n1, c, n2, pragma[only_bind_into](config)) and - revFlow(n2, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, config) and - exists(ap) - } - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, _, pragma[only_bind_into](config)) and - exists(state) and - exists(ap) - } - - private predicate throughFlowNodeCand(NodeEx node, Configuration config) { - revFlow(node, true, config) and - fwdFlow(node, true, config) and - not inBarrier(node, config) and - not outBarrier(node, config) - } - - /** Holds if flow may return from `callable`. */ - pragma[nomagic] - private predicate returnFlowCallableNodeCand( - DataFlowCallable callable, ReturnKindExt kind, Configuration config - ) { - exists(RetNodeEx ret | - throughFlowNodeCand(ret, config) and - callable = ret.getEnclosingCallable() and - kind = ret.getKind() - ) - } - - /** - * Holds if flow may enter through `p` and reach a return node making `p` a - * candidate for the origin of a summary. - */ - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(DataFlowCallable c, ReturnKindExt kind | - throughFlowNodeCand(p, config) and - returnFlowCallableNodeCand(c, kind, config) and - p.getEnclosingCallable() = c and - exists(ap) and - parameterFlowThroughAllowed(p, kind) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - throughFlowNodeCand(ret, config) and - kind = ret.getKind() and - exists(argAp) and - exists(ap) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, boolean toReturn | - revFlow(arg, toReturn, config) and - revFlowInToReturn(call, arg, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, config)) and - fields = count(Content f0 | fwdFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | fwdFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, config)) and - fields = count(Content f0 | revFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | revFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | revFlow(n, b, config)) - } - /* End: Stage 1 logic. */ -} - -pragma[noinline] -private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - localFlowStep(node1, node2, config) -} - -pragma[noinline] -private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - additionalLocalFlowStep(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCand1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config -) { - Stage1::revFlow(out, config) and - Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, Configuration config -) { - exists(ReturnPosition pos | - viableReturnPosOutNodeCand1(call, pos, out, config) and - pos = ret.getReturnPosition() and - kind = pos.getKind() and - Stage1::revFlow(ret, config) and - not outBarrier(ret, config) and - not inBarrier(out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCand1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config -) { - Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and - Stage1::revFlow(arg, config) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config -) { - viableParamArgNodeCand1(call, p, arg, config) and - Stage1::revFlow(p, config) and - not outBarrier(arg, config) and - not inBarrier(p, config) -} - -/** - * Gets the amount of forward branching on the origin of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int branch(NodeEx n1, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n1, _, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) - ) -} - -/** - * Gets the amount of backward branching on the target of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int join(NodeEx n2, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n, _, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) - ) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. The - * `allowsFieldFlow` flag indicates whether the branching is within the limit - * specified by the configuration. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, ret, kind, out, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(ret, pragma[only_bind_into](config)) and - j = join(out, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. The `allowsFieldFlow` flag indicates whether - * the branching is within the limit specified by the configuration. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config -) { - flowIntoCallNodeCand1(call, arg, p, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(arg, pragma[only_bind_into](config)) and - j = join(p, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -private signature module StageSig { - class Ap; - - predicate revFlow(NodeEx node, Configuration config); - - predicate revFlowAp(NodeEx node, Ap ap, Configuration config); - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config); - - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config); - - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config); - - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ); - - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ); - - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config); -} - -private module MkStage { - class ApApprox = PrevStage::Ap; - - signature module StageParam { - class Ap; - - class ApNil extends Ap; - - bindingset[result, ap] - ApApprox getApprox(Ap ap); - - ApNil getApNil(NodeEx node); - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail); - - /** - * An approximation of `Content` that corresponds to the precision level of - * `Ap`, such that the mappings from both `Ap` and `Content` to this type - * are functional. - */ - class ApHeadContent; - - ApHeadContent getHeadContent(Ap ap); - - ApHeadContent projectToHeadContent(Content c); - - class ApOption; - - ApOption apNone(); - - ApOption apSome(Ap ap); - - class Cc; - - class CcCall extends Cc; - - // TODO: member predicate on CcCall - predicate matchesCall(CcCall cc, DataFlowCall call); - - class CcNoCall extends Cc; - - Cc ccNone(); - - CcCall ccSomeCall(); - - class LocalCc; - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc); - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ); - - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config - ); - - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config - ); - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config); - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType); - } - - module Stage implements StageSig { - import Param - - /* Begin: Stage logic. */ - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - private predicate revFlowApAlias(NodeEx node, ApApprox apa, Configuration config) { - PrevStage::revFlowAp(node, apa, config) + exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") } - pragma[nomagic] - private predicate flowIntoCallApa( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa, - Configuration config - ) { - flowIntoCall(call, arg, p, allowsFieldFlow, config) and - PrevStage::revFlowAp(p, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(arg, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } + Node asNode() { this = TNodeNormal(result) } + + predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } + + Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } pragma[nomagic] - private predicate flowOutOfCallApa( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - ApApprox apa, Configuration config - ) { - flowOutOfCall(call, ret, kind, out, allowsFieldFlow, config) and - PrevStage::revFlowAp(out, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(ret, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowThroughOutOfCall( - DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, - ApApprox argApa, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa, pragma[only_bind_into](config)) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) and - matchesCall(ccc, call) - ) - } - - /** - * Holds if `node` is reachable with access path `ap` from a source in the - * configuration `config`. - * - * The call context `cc` records whether the node is reached through an - * argument in a call, and if so, `summaryCtx` and `argAp` record the - * corresponding parameter position and access path of that argument, respectively. - */ - pragma[nomagic] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa, config) and - PrevStage::revFlow(node, state, apa, config) and - filter(node, state, ap, config) + private DataFlowCallable getEnclosingCallable0() { + nodeEnclosingCallable(this.projectToNode(), result) } pragma[inline] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, cc, summaryCtx, argAp, ap, _, config) + DataFlowCallable getEnclosingCallable() { + pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) } pragma[nomagic] - private predicate fwdFlow0( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config + private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } + + pragma[inline] + DataFlowType getDataFlowType() { + pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) + } + + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn ) { - sourceNode(node, state, config) and - (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and - argAp = apNone() and - summaryCtx = TParamNodeNone() and - ap = getApNil(node) and - apa = getApprox(ap) + this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + private class ArgNodeEx extends NodeEx { + ArgNodeEx() { this.asNode() instanceof ArgNode } + } + + private class ParamNodeEx extends NodeEx { + ParamNodeEx() { this.asNode() instanceof ParamNode } + + predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { + this.asNode().(ParamNode).isParameterOf(c, pos) + } + + ParameterPosition getPosition() { this.isParameterOf(_, result) } + } + + private class RetNodeEx extends NodeEx { + RetNodeEx() { this.asNode() instanceof ReturnNodeExt } + + ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } + + ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } + } + + private predicate inBarrier(NodeEx node) { + exists(Node n | + node.asNode() = n and + Config::isBarrierIn(n) and + Config::isSource(n, _) + ) + } + + private predicate outBarrier(NodeEx node) { + exists(Node n | + node.asNode() = n and + Config::isBarrierOut(n) and + Config::isSink(n, _) + ) + } + + pragma[nomagic] + private predicate fullBarrier(NodeEx node) { + exists(Node n | node.asNode() = n | + Config::isBarrier(n) or - exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | - fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0, config) and - localCc = getLocalCc(mid, cc) - | - localStep(mid, state0, node, state, true, _, config, localCc) and - ap = ap0 and - apa = apa0 - or - localStep(mid, state0, node, state, false, ap, config, localCc) and - ap0 instanceof ApNil and - apa = getApprox(ap) + Config::isBarrierIn(n) and + not Config::isSource(n, _) + or + Config::isBarrierOut(n) and + not Config::isSink(n, _) + ) + } + + pragma[nomagic] + private predicate stateBarrier(NodeEx node, FlowState state) { + exists(Node n | node.asNode() = n | Config::isBarrier(n, state)) + } + + pragma[nomagic] + private predicate sourceNode(NodeEx node, FlowState state) { + Config::isSource(node.asNode(), state) and + not fullBarrier(node) and + not stateBarrier(node, state) + } + + pragma[nomagic] + private predicate sinkNode(NodeEx node, FlowState state) { + Config::isSink(node.asNode(), state) and + not fullBarrier(node) and + not stateBarrier(node, state) + } + + /** Provides the relevant barriers for a step from `node1` to `node2`. */ + pragma[inline] + private predicate stepFilter(NodeEx node1, NodeEx node2) { + not outBarrier(node1) and + not inBarrier(node2) and + not fullBarrier(node1) and + not fullBarrier(node2) + } + + /** + * Holds if data can flow in one local step from `node1` to `node2`. + */ + private predicate localFlowStepEx(NodeEx node1, NodeEx node2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and + stepFilter(node1, node2) + ) + or + exists(Node n | + Config::allowImplicitRead(n, _) and + node1.asNode() = n and + node2.isImplicitReadNode(n, false) and + not fullBarrier(node1) + ) + } + + /** + * Holds if the additional step from `node1` to `node2` does not jump between callables. + */ + private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + Config::isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and + getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and + stepFilter(node1, node2) + ) + or + exists(Node n | + Config::allowImplicitRead(n, _) and + node1.isImplicitReadNode(n, true) and + node2.asNode() = n and + not fullBarrier(node2) + ) + } + + private predicate additionalLocalStateStep(NodeEx node1, FlowState s1, NodeEx node2, FlowState s2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + Config::isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and + getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and + stepFilter(node1, node2) and + not stateBarrier(node1, s1) and + not stateBarrier(node2, s2) + ) + } + + /** + * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. + */ + private predicate jumpStepEx(NodeEx node1, NodeEx node2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and + stepFilter(node1, node2) and + not Config::getAFeature() instanceof FeatureEqualSourceSinkCallContext + ) + } + + /** + * Holds if the additional step from `node1` to `node2` jumps between callables. + */ + private predicate additionalJumpStep(NodeEx node1, NodeEx node2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + Config::isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and + getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and + stepFilter(node1, node2) and + not Config::getAFeature() instanceof FeatureEqualSourceSinkCallContext + ) + } + + private predicate additionalJumpStateStep(NodeEx node1, FlowState s1, NodeEx node2, FlowState s2) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + Config::isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and + getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and + stepFilter(node1, node2) and + not stateBarrier(node1, s1) and + not stateBarrier(node2, s2) and + not Config::getAFeature() instanceof FeatureEqualSourceSinkCallContext + ) + } + + pragma[nomagic] + private predicate readSetEx(NodeEx node1, ContentSet c, NodeEx node2) { + readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and + stepFilter(node1, node2) + or + exists(Node n | + node2.isImplicitReadNode(n, true) and + node1.isImplicitReadNode(n, _) and + Config::allowImplicitRead(n, c) + ) + } + + // inline to reduce fan-out via `getAReadContent` + bindingset[c] + private predicate read(NodeEx node1, Content c, NodeEx node2) { + exists(ContentSet cs | + readSetEx(node1, cs, node2) and + pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() + ) + } + + // inline to reduce fan-out via `getAReadContent` + bindingset[c] + private predicate clearsContentEx(NodeEx n, Content c) { + exists(ContentSet cs | + clearsContentCached(n.asNode(), cs) and + pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() + ) + } + + // inline to reduce fan-out via `getAReadContent` + bindingset[c] + private predicate expectsContentEx(NodeEx n, Content c) { + exists(ContentSet cs | + expectsContentCached(n.asNode(), cs) and + pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() + ) + } + + pragma[nomagic] + private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } + + pragma[nomagic] + private predicate hasReadStep(Content c) { read(_, c, _) } + + pragma[nomagic] + private predicate storeEx(NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType) { + store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), + contentType) and + hasReadStep(tc.getContent()) and + stepFilter(node1, node2) + } + + pragma[nomagic] + private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { + viableReturnPosOut(call, pos, out.asNode()) + } + + pragma[nomagic] + private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { + viableParamArg(call, p.asNode(), arg.asNode()) + } + + /** + * Holds if field flow should be used for the given configuration. + */ + private predicate useFieldFlow() { Config::fieldFlowBranchLimit() >= 1 } + + private predicate hasSourceCallCtx() { + exists(FlowFeature feature | feature = Config::getAFeature() | + feature instanceof FeatureHasSourceCallContext or + feature instanceof FeatureEqualSourceSinkCallContext + ) + } + + private predicate hasSinkCallCtx() { + exists(FlowFeature feature | feature = Config::getAFeature() | + feature instanceof FeatureHasSinkCallContext or + feature instanceof FeatureEqualSourceSinkCallContext + ) + } + + /** + * Holds if flow from `p` to a return node of kind `kind` is allowed. + * + * We don't expect a parameter to return stored in itself, unless + * explicitly allowed + */ + bindingset[p, kind] + private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { + exists(ParameterPosition pos | p.isParameterOf(_, pos) | + not kind.(ParamUpdateReturnKind).getPosition() = pos + or + allowParameterReturnInSelfCached(p.asNode()) + ) + } + + private module Stage1 implements StageSig { + class Ap extends int { + // workaround for bad functionality-induced joins (happens when using `Unit`) + pragma[nomagic] + Ap() { this in [0 .. 1] and this < 1 } + } + + private class Cc = boolean; + + /* Begin: Stage 1 logic. */ + /** + * Holds if `node` is reachable from a source. + * + * The Boolean `cc` records whether the node is reached through an + * argument in a call. + */ + private predicate fwdFlow(NodeEx node, Cc cc) { + sourceNode(node, _) and + if hasSourceCallCtx() then cc = true else cc = false + or + exists(NodeEx mid | fwdFlow(mid, cc) | + localFlowStepEx(mid, node) or + additionalLocalFlowStep(mid, node) or + additionalLocalStateStep(mid, _, node, _) ) or - exists(NodeEx mid | - fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa, pragma[only_bind_into](config)) and - jumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(mid, state, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(mid, state0, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStateStep(mid, state0, node, state, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) + exists(NodeEx mid | fwdFlow(mid, _) and cc = false | + jumpStepEx(mid, node) or + additionalJumpStep(mid, node) or + additionalJumpStateStep(mid, _, node, _) ) or // store - exists(TypedContent tc, Ap ap0 | - fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp, config) and - ap = apCons(tc, ap0) and - apa = getApprox(ap) + exists(NodeEx mid | + useFieldFlow() and + fwdFlow(mid, cc) and + storeEx(mid, _, node, _) ) or // read - exists(Ap ap0, Content c | - fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp, config) and - fwdFlowConsCand(ap0, c, ap, config) and - apa = getApprox(ap) + exists(ContentSet c | + fwdFlowReadSet(c, node, cc) and + fwdFlowConsCandSet(c, _) ) or // flow into a callable - fwdFlowIn(_, node, state, _, cc, _, _, ap, apa, config) and - if PrevStage::parameterMayFlowThrough(node, apa, config) - then ( - summaryCtx = TParamNodeSome(node.asNode()) and - argAp = apSome(ap) - ) else ( - summaryCtx = TParamNodeNone() and argAp = apNone() - ) + fwdFlowIn(_, _, _, node) and + cc = true or // flow out of a callable - exists( - DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, - DataFlowCallable inner - | - fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa, config) and - flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa, config) and - inner = ret.getEnclosingCallable() and - cc = getCallContextReturn(inner, call, innercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) + fwdFlowOut(_, node, false) and + cc = false or // flow through a callable - exists( - DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, ApApprox innerArgApa - | - fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowStore( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(DataFlowType contentType, ApApprox apa1 | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1, config) and - PrevStage::storeStepCand(node1, apa1, tc, node2, contentType, config) and - typecheckStore(ap1, contentType) - ) - } - - /** - * Holds if forward flow with access path `tail` reaches a store of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(TypedContent tc | - fwdFlowStore(_, tail, tc, _, _, _, _, _, config) and - tc.getContent() = c and - cons = apCons(tc, tail) - ) - } - - pragma[nomagic] - private predicate readStepCand( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - PrevStage::readStepCand(node1, c, node2, config) and - apc = projectToHeadContent(c) - } - - bindingset[node1, apc] - pragma[inline_late] - private predicate readStepCand0( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - readStepCand(node1, apc, c, node2, config) - } - - pragma[nomagic] - private predicate fwdFlowRead( - Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(ApHeadContent apc | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap, config) and - apc = getHeadContent(ap) and - readStepCand0(node1, apc, c, node2, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowIn( - DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, - ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa, Configuration config - ) { - exists(ArgNodeEx arg, boolean allowsFieldFlow | - fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa, config) and - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowRetFromArg( - RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, ApApprox argApa, - Ap ap, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - fwdFlow(pragma[only_bind_into](ret), state, ccc, - TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), - pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa), - pragma[only_bind_into](config)) and - kind = ret.getKind() and - parameterFlowThroughAllowed(summaryCtx, kind) and - argApa = getApprox(argAp) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) + exists(DataFlowCall call | + fwdFlowOutFromArg(call, node) and + fwdFlowIsEntered(call, cc) ) } + // inline to reduce the number of iterations pragma[inline] - private predicate fwdFlowThrough0( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, - Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa, config) and - fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp, config) - } - - pragma[nomagic] - private predicate fwdFlowThrough( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa, - config) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` - * and data might flow through the target callable and back out at `call`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered( - DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, - ParamNodeEx p, Ap ap, Configuration config - ) { - exists(ApApprox apa | - fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(p, apa, config) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate storeStepFwd( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config - ) { - fwdFlowStore(node1, ap1, tc, node2, _, _, _, _, config) and - ap2 = apCons(tc, ap1) and - fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _, config) - } - - private predicate readStepFwd( - NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config - ) { - fwdFlowRead(ap1, c, n1, n2, _, _, _, _, config) and - fwdFlowConsCand(ap1, c, ap2, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough0( - DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, - ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, - innerArgApa, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough( - RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, - Ap ap, Configuration config - ) { - exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | - returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa, config) and - pos = ret.getReturnPosition() and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowThroughIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap, - Configuration config - ) { - exists(ApApprox argApa | - flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), - allowsFieldFlow, argApa, pragma[only_bind_into](config)) and - fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa, - pragma[only_bind_into](config)) and - returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap, - pragma[only_bind_into](config)) and - if allowsFieldFlow = false then argAp instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowIntoCallAp( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap, - Configuration config - ) { - exists(ApApprox apa | - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - fwdFlow(arg, _, _, _, _, ap, apa, config) - ) - } - - pragma[nomagic] - private predicate flowOutOfCallAp( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, - Ap ap, Configuration config - ) { - exists(ApApprox apa | - flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa, config) and - fwdFlow(ret, _, _, _, _, ap, apa, config) and - pos = ret.getReturnPosition() - ) - } - - /** - * Holds if `node` with access path `ap` is part of a path from a source to a - * sink in the configuration `config`. - * - * The parameter `returnCtx` records whether (and how) the node must be returned - * from the enclosing callable in order to reach a sink, and if so, `returnAp` - * records the access path of the returned value. - */ - pragma[nomagic] - additional predicate revFlow( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - revFlow0(node, state, returnCtx, returnAp, ap, config) and - fwdFlow(node, state, _, _, _, ap, config) - } - - pragma[nomagic] - private predicate revFlow0( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, _, _, _, ap, config) and - sinkNode(node, state, config) and + private predicate fwdFlowIn(DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p) { + // call context cannot help reduce virtual dispatch + fwdFlow(arg, cc) and + viableParamArgEx(call, p, arg) and + not fullBarrier(p) and ( - if hasSinkCallCtx(config) - then returnCtx = TReturnCtxNoFlowThrough() - else returnCtx = TReturnCtxNone() - ) and - returnAp = apNone() and - ap instanceof ApNil - or - exists(NodeEx mid, FlowState state0 | - localStep(node, state, mid, state0, true, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, ap, config) + cc = false + or + cc = true and + not reducedViableImplInCallContext(call, _, _) ) or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap, pragma[only_bind_into](config)) and - localStep(node, pragma[only_bind_into](state), mid, state0, false, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, nil, pragma[only_bind_into](config)) and - ap instanceof ApNil + // call context may help reduce virtual dispatch + exists(DataFlowCallable target | + fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target) and + target = viableImplInSomeFwdFlowCallContextExt(call) and + cc = true ) - or + } + + /** + * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. + */ + pragma[nomagic] + private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc) { fwdFlowIn(call, _, cc, _) } + + pragma[nomagic] + private predicate fwdFlowInReducedViableImplInSomeCallContext( + DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target + ) { + fwdFlow(arg, true) and + viableParamArgEx(call, p, arg) and + reducedViableImplInCallContext(call, _, _) and + target = p.getEnclosingCallable() and + not fullBarrier(p) + } + + /** + * Gets a viable dispatch target of `call` in the context `ctx`. This is + * restricted to those `call`s for which a context might make a difference, + * and to `ctx`s that are reachable in `fwdFlow`. + */ + pragma[nomagic] + private DataFlowCallable viableImplInSomeFwdFlowCallContextExt(DataFlowCall call) { + exists(DataFlowCall ctx | + fwdFlowIsEntered(ctx, _) and + result = viableImplInCallContextExt(call, ctx) + ) + } + + private predicate fwdFlow(NodeEx node) { fwdFlow(node, _) } + + pragma[nomagic] + private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc) { exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, state, _, _, ap, config) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() + fwdFlow(mid, cc) and + readSetEx(mid, c, node) + ) + } + + /** + * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. + */ + pragma[assume_small_delta] + pragma[nomagic] + private predicate fwdFlowConsCand(Content c) { + exists(NodeEx mid, NodeEx node, TypedContent tc | + not fullBarrier(node) and + useFieldFlow() and + fwdFlow(mid, _) and + storeEx(mid, tc, node, _) and + c = tc.getContent() + ) + } + + /** + * Holds if `cs` may be interpreted in a read as the target of some store + * into `c`, in the flow covered by `fwdFlow`. + */ + pragma[nomagic] + private predicate fwdFlowConsCandSet(ContentSet cs, Content c) { + fwdFlowConsCand(c) and + c = cs.getAReadContent() + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc) { + exists(RetNodeEx ret | + fwdFlow(ret, cc) and + ret.getReturnPosition() = pos + ) + } + + // inline to reduce the number of iterations + pragma[inline] + private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc) and + viableReturnPosOutEx(call, pos, out) and + not fullBarrier(out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out) { + fwdFlowOut(call, out, true) + } + + private predicate stateStepFwd(FlowState state1, FlowState state2) { + exists(NodeEx node1 | + additionalLocalStateStep(node1, state1, _, state2) or + additionalJumpStateStep(node1, state1, _, state2) + | + fwdFlow(node1) + ) + } + + private predicate fwdFlowState(FlowState state) { + sourceNode(_, state) + or + exists(FlowState state0 | + fwdFlowState(state0) and + stateStepFwd(state0, state) + ) + } + + /** + * Holds if `node` is part of a path from a source to a sink. + * + * The Boolean `toReturn` records whether the node must be returned from + * the enclosing callable in order to reach a sink. + */ + pragma[nomagic] + private predicate revFlow(NodeEx node, boolean toReturn) { + revFlow0(node, toReturn) and + fwdFlow(node) + } + + pragma[nomagic] + private predicate revFlow0(NodeEx node, boolean toReturn) { + exists(FlowState state | + fwdFlow(node) and + sinkNode(node, state) and + fwdFlowState(state) and + if hasSinkCallCtx() then toReturn = true else toReturn = false ) or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStep(node, mid, config) and - revFlow(pragma[only_bind_into](mid), state, _, _, nil, pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil + exists(NodeEx mid | revFlow(mid, toReturn) | + localFlowStepEx(node, mid) or + additionalLocalFlowStep(node, mid) or + additionalLocalStateStep(node, _, mid, _) ) or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStateStep(node, state, mid, state0, config) and - revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil, - pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil + exists(NodeEx mid | revFlow(mid, _) and toReturn = false | + jumpStepEx(node, mid) or + additionalJumpStep(node, mid) or + additionalJumpStateStep(node, _, mid, _) ) or // store - exists(Ap ap0, Content c | - revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp, config) and - revFlowConsCand(ap0, c, ap, config) + exists(Content c | + revFlowStore(c, node, toReturn) and + revFlowConsCand(c) ) or // read - exists(NodeEx mid, Ap ap0 | - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - readStepFwd(node, ap, _, mid, ap0, config) + exists(NodeEx mid, ContentSet c | + readSetEx(node, c, mid) and + fwdFlowConsCandSet(c, _) and + revFlow(mid, toReturn) ) or // flow into a callable - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, state, TReturnCtxNone(), returnAp, ap, config) and - flowIntoCallAp(_, node, p, allowsFieldFlow, ap, config) and - (if allowsFieldFlow = false then ap instanceof ApNil else any()) and - returnCtx = TReturnCtxNone() - ) - or - // flow through a callable - exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, node, p, _, ap, innerReturnAp, config) - ) + revFlowIn(_, node, false) and + toReturn = false or // flow out of a callable exists(ReturnPosition pos | - revFlowOut(_, node, pos, state, _, _, ap, config) and - if returnFlowsThrough(node, pos, state, _, _, _, ap, config) - then ( - returnCtx = TReturnCtxMaybeFlowThrough(pos) and - returnAp = apSome(ap) - ) else ( - returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() - ) + revFlowOut(pos) and + node.(RetNodeEx).getReturnPosition() = pos and + toReturn = true + ) + or + // flow through a callable + exists(DataFlowCall call | + revFlowInToReturn(call, node) and + revFlowIsReturned(call, toReturn) ) - } - - pragma[nomagic] - private predicate revFlowStore( - Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, - ReturnCtx returnCtx, ApOption returnAp, Configuration config - ) { - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - storeStepFwd(node, ap, tc, mid, ap0, config) and - tc.getContent() = c } /** - * Holds if reverse flow with access path `tail` reaches a read of `c` - * resulting in access path `cons`. + * Holds if `c` is the target of a read in the flow covered by `revFlow`. */ pragma[nomagic] - private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(NodeEx mid, Ap tail0 | - revFlow(mid, _, _, _, tail, config) and - tail = pragma[only_bind_into](tail0) and - readStepFwd(_, cons, c, mid, tail0, config) + private predicate revFlowConsCand(Content c) { + exists(NodeEx mid, NodeEx node, ContentSet cs | + fwdFlow(node) and + readSetEx(node, cs, mid) and + fwdFlowConsCandSet(cs, c) and + revFlow(pragma[only_bind_into](mid), _) ) } pragma[nomagic] - private predicate revFlowOut( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, - ApOption returnAp, Ap ap, Configuration config + private predicate revFlowStore(Content c, NodeEx node, boolean toReturn) { + exists(NodeEx mid, TypedContent tc | + revFlow(mid, toReturn) and + fwdFlowConsCand(c) and + storeEx(node, tc, mid, _) and + c = tc.getContent() + ) + } + + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + pragma[nomagic] + additional predicate revFlowIsReadAndStored(Content c) { + revFlowConsCand(c) and + revFlowStore(c, _, _) + } + + pragma[nomagic] + additional predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, NodeEx out ) { - exists(NodeEx out, boolean allowsFieldFlow | - revFlow(out, state, returnCtx, returnAp, ap, config) and - flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() + fwdFlowReturnPosition(pos, _) and + viableReturnPosOutEx(call, pos, out) + } + + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos) { + exists(NodeEx out | + revFlow(out, _) and + viableReturnPosOutNodeCandFwd1(_, pos, out) ) } pragma[nomagic] - private predicate revFlowParamToReturn( - ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap, Configuration config - ) { - revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), - pragma[only_bind_into](ap), pragma[only_bind_into](config)) and - parameterFlowThroughAllowed(p, pos.getKind()) and - PrevStage::parameterMayFlowThrough(p, getApprox(ap), config) + additional predicate viableParamArgNodeCandFwd1(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { + fwdFlowIn(call, arg, _, p) + } + + // inline to reduce the number of iterations + pragma[inline] + private predicate revFlowIn(DataFlowCall call, ArgNodeEx arg, boolean toReturn) { + exists(ParamNodeEx p | + revFlow(p, toReturn) and + viableParamArgNodeCandFwd1(call, p, arg) + ) } pragma[nomagic] - private predicate revFlowThrough( - DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, - ApOption returnAp, Ap ap, Ap innerReturnAp, Configuration config - ) { - revFlowParamToReturn(p, state, pos, innerReturnAp, ap, config) and - revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp, config) + private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg) { + revFlowIn(call, arg, true) } /** @@ -1866,896 +766,1704 @@ private module MkStage { * reaching an argument of `call`. */ pragma[nomagic] - private predicate revFlowIsReturned( - DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap, - Configuration config - ) { - exists(RetNodeEx ret, FlowState state, CcCall ccc | - revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap, config) and - returnFlowsThrough(ret, pos, state, ccc, _, _, ap, config) and - matchesCall(ccc, call) + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn) { + exists(NodeEx out | + revFlow(out, toReturn) and + fwdFlowOutFromArg(call, out) + ) + } + + private predicate stateStepRev(FlowState state1, FlowState state2) { + exists(NodeEx node1, NodeEx node2 | + additionalLocalStateStep(node1, state1, node2, state2) or + additionalJumpStateStep(node1, state1, node2, state2) + | + revFlow(node1, _) and + revFlow(node2, _) and + fwdFlowState(state1) and + fwdFlowState(state2) + ) + } + + pragma[nomagic] + additional predicate revFlowState(FlowState state) { + exists(NodeEx node | + sinkNode(node, state) and + revFlow(node, _) and + fwdFlowState(state) + ) + or + exists(FlowState state0 | + revFlowState(state0) and + stateStepRev(state, state0) ) } pragma[nomagic] predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType ) { - exists(Ap ap2, Content c | - PrevStage::storeStepCand(node1, _, tc, node2, contentType, config) and - revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _, config) and - revFlowConsCand(ap2, c, ap1, config) - ) - } - - predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(Ap ap1, Ap ap2 | - revFlow(node2, _, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and - readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _, - pragma[only_bind_into](config)) - ) - } - - additional predicate revFlow(NodeEx node, FlowState state, Configuration config) { - revFlow(node, state, _, _, _, config) - } - - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, _, _, ap, config) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, _, _, _, ap, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, Configuration config) { - revFlow(node, _, _, _, _, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, ap, config) - } - - private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepFwd(_, ap, tc, _, _, config) - } - - private predicate revConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepCand(_, ap, tc, _, _, config) - } - - private predicate validAp(Ap ap, Configuration config) { - revFlow(_, _, _, _, ap, config) and ap instanceof ApNil - or - exists(TypedContent head, Ap tail | - consCand(head, tail, config) and - ap = apCons(head, tail) - ) - } - - additional predicate consCand(TypedContent tc, Ap ap, Configuration config) { - revConsCand(tc, ap, config) and - validAp(ap, config) - } - - pragma[nomagic] - private predicate parameterFlowsThroughRev( - ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp, Configuration config - ) { - revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap, config) and - parameterFlowThroughAllowed(p, pos.getKind()) - } - - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(ReturnPosition pos | - returnFlowsThrough(_, pos, _, _, p, ap, _, config) and - parameterFlowsThroughRev(p, ap, pos, _, config) + exists(Content c | + revFlowIsReadAndStored(c) and + revFlow(node2) and + storeEx(node1, tc, node2, contentType) and + c = tc.getContent() and + exists(ap1) ) } pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - exists(ParamNodeEx p, ReturnPosition pos | - returnFlowsThrough(ret, pos, _, _, p, argAp, ap, config) and - parameterFlowsThroughRev(p, argAp, pos, ap, config) and - kind = pos.getKind() + predicate readStepCand(NodeEx n1, Content c, NodeEx n2) { + revFlowIsReadAndStored(c) and + read(n1, c, n2) and + revFlow(n2) + } + + pragma[nomagic] + predicate revFlow(NodeEx node) { revFlow(node, _) } + + pragma[nomagic] + predicate revFlowAp(NodeEx node, Ap ap) { + revFlow(node) and + exists(ap) + } + + bindingset[node, state] + predicate revFlow(NodeEx node, FlowState state, Ap ap) { + revFlow(node, _) and + exists(state) and + exists(ap) + } + + private predicate throughFlowNodeCand(NodeEx node) { + revFlow(node, true) and + fwdFlow(node, true) and + not inBarrier(node) and + not outBarrier(node) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand(DataFlowCallable callable, ReturnKindExt kind) { + exists(RetNodeEx ret | + throughFlowNodeCand(ret) and + callable = ret.getEnclosingCallable() and + kind = ret.getKind() + ) + } + + /** + * Holds if flow may enter through `p` and reach a return node making `p` a + * candidate for the origin of a summary. + */ + pragma[nomagic] + predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap) { + exists(DataFlowCallable c, ReturnKindExt kind | + throughFlowNodeCand(p) and + returnFlowCallableNodeCand(c, kind) and + p.getEnclosingCallable() = c and + exists(ap) and + parameterFlowThroughAllowed(p, kind) ) } pragma[nomagic] - private predicate revFlowThroughArg( - DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, - Ap ap, Configuration config - ) { - exists(ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp, config) - ) + predicate returnMayFlowThrough(RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind) { + throughFlowNodeCand(ret) and + kind = ret.getKind() and + exists(argAp) and + exists(ap) } pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | - revFlow(arg, state, returnCtx, returnAp, ap, config) and - revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap, config) + predicate callMayFlowThroughRev(DataFlowCall call) { + exists(ArgNodeEx arg, boolean toReturn | + revFlow(arg, toReturn) and + revFlowInToReturn(call, arg) and + revFlowIsReturned(call, toReturn) ) } additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config + boolean fwd, int nodes, int fields, int conscand, int states, int tuples ) { fwd = true and - nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, config)) and - fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and - states = count(FlowState state | fwdFlow(_, state, _, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | - fwdFlow(n, state, cc, summaryCtx, argAp, ap, config) - ) + nodes = count(NodeEx node | fwdFlow(node)) and + fields = count(Content f0 | fwdFlowConsCand(f0)) and + conscand = -1 and + states = count(FlowState state | fwdFlowState(state)) and + tuples = count(NodeEx n, boolean b | fwdFlow(n, b)) or fwd = false and - nodes = count(NodeEx node | revFlow(node, _, _, _, _, config)) and - fields = count(TypedContent f0 | consCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and - states = count(FlowState state | revFlow(_, state, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | - revFlow(n, state, returnCtx, retAp, ap, config) - ) + nodes = count(NodeEx node | revFlow(node, _)) and + fields = count(Content f0 | revFlowConsCand(f0)) and + conscand = -1 and + states = count(FlowState state | revFlowState(state)) and + tuples = count(NodeEx n, boolean b | revFlow(n, b)) } - /* End: Stage logic. */ - } -} - -private module BooleanCallContext { - class Cc extends boolean { - Cc() { this in [true, false] } + /* End: Stage 1 logic. */ } - class CcCall extends Cc { - CcCall() { this = true } + pragma[noinline] + private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2) { + Stage1::revFlow(node2) and + localFlowStepEx(node1, node2) } - /** Holds if the call context may be `call`. */ - predicate matchesCall(CcCall cc, DataFlowCall call) { any() } - - class CcNoCall extends Cc { - CcNoCall() { this = false } + pragma[noinline] + private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2) { + Stage1::revFlow(node2) and + additionalLocalFlowStep(node1, node2) } - Cc ccNone() { result = false } + pragma[nomagic] + private predicate viableReturnPosOutNodeCand1(DataFlowCall call, ReturnPosition pos, NodeEx out) { + Stage1::revFlow(out) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out) + } - CcCall ccSomeCall() { result = true } + /** + * Holds if data can flow out of `call` from `ret` to `out`, either + * through a `ReturnNode` or through an argument that has been mutated, and + * that this step is part of a path from a source to a sink. + */ + pragma[nomagic] + private predicate flowOutOfCallNodeCand1( + DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out + ) { + exists(ReturnPosition pos | + viableReturnPosOutNodeCand1(call, pos, out) and + pos = ret.getReturnPosition() and + kind = pos.getKind() and + Stage1::revFlow(ret) and + not outBarrier(ret) and + not inBarrier(out) + ) + } - class LocalCc = Unit; + pragma[nomagic] + private predicate viableParamArgNodeCand1(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { + Stage1::viableParamArgNodeCandFwd1(call, p, arg) and + Stage1::revFlow(arg) + } - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } + /** + * Holds if data can flow into `call` and that this step is part of a + * path from a source to a sink. + */ + pragma[nomagic] + private predicate flowIntoCallNodeCand1(DataFlowCall call, ArgNodeEx arg, ParamNodeEx p) { + viableParamArgNodeCand1(call, p, arg) and + Stage1::revFlow(p) and + not outBarrier(arg) and + not inBarrier(p) + } - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + /** + * Gets the amount of forward branching on the origin of a cross-call path + * edge in the graph of paths between sources and sinks that ignores call + * contexts. + */ + pragma[nomagic] + private int branch(NodeEx n1) { + result = + strictcount(NodeEx n | flowOutOfCallNodeCand1(_, n1, _, n) or flowIntoCallNodeCand1(_, n1, n)) + } - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } -} + /** + * Gets the amount of backward branching on the target of a cross-call path + * edge in the graph of paths between sources and sinks that ignores call + * contexts. + */ + pragma[nomagic] + private int join(NodeEx n2) { + result = + strictcount(NodeEx n | flowOutOfCallNodeCand1(_, n, _, n2) or flowIntoCallNodeCand1(_, n, n2)) + } -private module Level1CallContext { - class Cc = CallContext; + /** + * Holds if data can flow out of `call` from `ret` to `out`, either + * through a `ReturnNode` or through an argument that has been mutated, and + * that this step is part of a path from a source to a sink. The + * `allowsFieldFlow` flag indicates whether the branching is within the limit + * specified by the configuration. + */ + pragma[nomagic] + private predicate flowOutOfCallNodeCand1( + DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow + ) { + flowOutOfCallNodeCand1(call, ret, kind, out) and + exists(int b, int j | + b = branch(ret) and + j = join(out) and + if b.minimum(j) <= Config::fieldFlowBranchLimit() + then allowsFieldFlow = true + else allowsFieldFlow = false + ) + } - class CcCall = CallContextCall; + /** + * Holds if data can flow into `call` and that this step is part of a + * path from a source to a sink. The `allowsFieldFlow` flag indicates whether + * the branching is within the limit specified by the configuration. + */ + pragma[nomagic] + private predicate flowIntoCallNodeCand1( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow + ) { + flowIntoCallNodeCand1(call, arg, p) and + exists(int b, int j | + b = branch(arg) and + j = join(p) and + if b.minimum(j) <= Config::fieldFlowBranchLimit() + then allowsFieldFlow = true + else allowsFieldFlow = false + ) + } - pragma[inline] - predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } + private signature module StageSig { + class Ap; - class CcNoCall = CallContextNoCall; + predicate revFlow(NodeEx node); - Cc ccNone() { result instanceof CallContextAny } + predicate revFlowAp(NodeEx node, Ap ap); - CcCall ccSomeCall() { result instanceof CallContextSomeCall } + bindingset[node, state] + predicate revFlow(NodeEx node, FlowState state, Ap ap); + + predicate callMayFlowThroughRev(DataFlowCall call); + + predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap); + + predicate returnMayFlowThrough(RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind); + + predicate storeStepCand( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType + ); + + predicate readStepCand(NodeEx n1, Content c, NodeEx n2); + } + + private module MkStage { + class ApApprox = PrevStage::Ap; + + signature module StageParam { + class Ap; + + class ApNil extends Ap; + + bindingset[result, ap] + ApApprox getApprox(Ap ap); + + ApNil getApNil(NodeEx node); + + bindingset[tc, tail] + Ap apCons(TypedContent tc, Ap tail); + + /** + * An approximation of `Content` that corresponds to the precision level of + * `Ap`, such that the mappings from both `Ap` and `Content` to this type + * are functional. + */ + class ApHeadContent; + + ApHeadContent getHeadContent(Ap ap); + + ApHeadContent projectToHeadContent(Content c); + + class ApOption; + + ApOption apNone(); + + ApOption apSome(Ap ap); + + class Cc; + + class CcCall extends Cc; + + // TODO: member predicate on CcCall + predicate matchesCall(CcCall cc, DataFlowCall call); + + class CcNoCall extends Cc; + + Cc ccNone(); + + CcCall ccSomeCall(); + + class LocalCc; + + bindingset[call, c, outercc] + CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); + + bindingset[call, c, innercc] + CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); + + bindingset[node, cc] + LocalCc getLocalCc(NodeEx node, Cc cc); + + bindingset[node1, state1] + bindingset[node2, state2] + predicate localStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + ApNil ap, LocalCc lcc + ); + + predicate flowOutOfCall( + DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow + ); + + predicate flowIntoCall( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow + ); + + bindingset[node, state, ap] + predicate filter(NodeEx node, FlowState state, Ap ap); + + bindingset[ap, contentType] + predicate typecheckStore(Ap ap, DataFlowType contentType); + } + + module Stage implements StageSig { + import Param + + /* Begin: Stage logic. */ + // use an alias as a workaround for bad functionality-induced joins + pragma[nomagic] + private predicate revFlowApAlias(NodeEx node, ApApprox apa) { + PrevStage::revFlowAp(node, apa) + } + + pragma[nomagic] + private predicate flowIntoCallApa( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa + ) { + flowIntoCall(call, arg, p, allowsFieldFlow) and + PrevStage::revFlowAp(p, pragma[only_bind_into](apa)) and + revFlowApAlias(arg, pragma[only_bind_into](apa)) + } + + pragma[nomagic] + private predicate flowOutOfCallApa( + DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, + ApApprox apa + ) { + flowOutOfCall(call, ret, kind, out, allowsFieldFlow) and + PrevStage::revFlowAp(out, pragma[only_bind_into](apa)) and + revFlowApAlias(ret, pragma[only_bind_into](apa)) + } + + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, + ApApprox argApa, ApApprox apa + ) { + exists(ReturnKindExt kind | + flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa) and + PrevStage::callMayFlowThroughRev(call) and + PrevStage::returnMayFlowThrough(ret, argApa, apa, kind) and + matchesCall(ccc, call) + ) + } + + /** + * Holds if `node` is reachable with access path `ap` from a source. + * + * The call context `cc` records whether the node is reached through an + * argument in a call, and if so, `summaryCtx` and `argAp` record the + * corresponding parameter position and access path of that argument, respectively. + */ + pragma[nomagic] + additional predicate fwdFlow( + NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, + ApApprox apa + ) { + fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa) and + PrevStage::revFlow(node, state, apa) and + filter(node, state, ap) + } + + pragma[inline] + additional predicate fwdFlow( + NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap + ) { + fwdFlow(node, state, cc, summaryCtx, argAp, ap, _) + } + + pragma[assume_small_delta] + pragma[nomagic] + private predicate fwdFlow0( + NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, + ApApprox apa + ) { + sourceNode(node, state) and + (if hasSourceCallCtx() then cc = ccSomeCall() else cc = ccNone()) and + argAp = apNone() and + summaryCtx = TParamNodeNone() and + ap = getApNil(node) and + apa = getApprox(ap) + or + exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | + fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0) and + localCc = getLocalCc(mid, cc) + | + localStep(mid, state0, node, state, true, _, localCc) and + ap = ap0 and + apa = apa0 + or + localStep(mid, state0, node, state, false, ap, localCc) and + ap0 instanceof ApNil and + apa = getApprox(ap) + ) + or + exists(NodeEx mid | + fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa) and + jumpStepEx(mid, node) and + cc = ccNone() and + summaryCtx = TParamNodeNone() and + argAp = apNone() + ) + or + exists(NodeEx mid, ApNil nil | + fwdFlow(mid, state, _, _, _, nil) and + additionalJumpStep(mid, node) and + cc = ccNone() and + summaryCtx = TParamNodeNone() and + argAp = apNone() and + ap = getApNil(node) and + apa = getApprox(ap) + ) + or + exists(NodeEx mid, FlowState state0, ApNil nil | + fwdFlow(mid, state0, _, _, _, nil) and + additionalJumpStateStep(mid, state0, node, state) and + cc = ccNone() and + summaryCtx = TParamNodeNone() and + argAp = apNone() and + ap = getApNil(node) and + apa = getApprox(ap) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp) and + ap = apCons(tc, ap0) and + apa = getApprox(ap) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp) and + fwdFlowConsCand(ap0, c, ap) and + apa = getApprox(ap) + ) + or + // flow into a callable + fwdFlowIn(_, node, state, _, cc, _, _, ap, apa) and + if PrevStage::parameterMayFlowThrough(node, apa) + then ( + summaryCtx = TParamNodeSome(node.asNode()) and + argAp = apSome(ap) + ) else ( + summaryCtx = TParamNodeNone() and argAp = apNone() + ) + or + // flow out of a callable + exists( + DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | + fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa) and + flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa) and + inner = ret.getEnclosingCallable() and + cc = getCallContextReturn(inner, call, innercc) and + if allowsFieldFlow = false then ap instanceof ApNil else any() + ) + or + // flow through a callable + exists( + DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, + ApApprox innerArgApa + | + fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa) and + flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa) and + if allowsFieldFlow = false then ap instanceof ApNil else any() + ) + } + + pragma[nomagic] + private predicate fwdFlowStore( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, + ParamNodeOption summaryCtx, ApOption argAp + ) { + exists(DataFlowType contentType, ApApprox apa1 | + fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1) and + PrevStage::storeStepCand(node1, apa1, tc, node2, contentType) and + typecheckStore(ap1, contentType) + ) + } + + /** + * Holds if forward flow with access path `tail` reaches a store of `c` + * resulting in access path `cons`. + */ + pragma[nomagic] + private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail) { + exists(TypedContent tc | + fwdFlowStore(_, tail, tc, _, _, _, _, _) and + tc.getContent() = c and + cons = apCons(tc, tail) + ) + } + + pragma[nomagic] + private predicate readStepCand(NodeEx node1, ApHeadContent apc, Content c, NodeEx node2) { + PrevStage::readStepCand(node1, c, node2) and + apc = projectToHeadContent(c) + } + + bindingset[node1, apc] + pragma[inline_late] + private predicate readStepCand0(NodeEx node1, ApHeadContent apc, Content c, NodeEx node2) { + readStepCand(node1, apc, c, node2) + } + + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, + ParamNodeOption summaryCtx, ApOption argAp + ) { + exists(ApHeadContent apc | + fwdFlow(node1, state, cc, summaryCtx, argAp, ap) and + apc = getHeadContent(ap) and + readStepCand0(node1, apc, c, node2) + ) + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, + ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa + ) { + exists(ArgNodeEx arg, boolean allowsFieldFlow | + fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa) and + flowIntoCallApa(call, arg, p, allowsFieldFlow, apa) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and + if allowsFieldFlow = false then ap instanceof ApNil else any() + ) + } + + pragma[nomagic] + private predicate fwdFlowRetFromArg( + RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, + ApApprox argApa, Ap ap, ApApprox apa + ) { + exists(ReturnKindExt kind | + fwdFlow(pragma[only_bind_into](ret), state, ccc, + TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), + pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa)) and + kind = ret.getKind() and + parameterFlowThroughAllowed(summaryCtx, kind) and + argApa = getApprox(argAp) and + PrevStage::returnMayFlowThrough(ret, argApa, apa, kind) + ) + } + + pragma[inline] + private predicate fwdFlowThrough0( + DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, + ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, + Ap innerArgAp, ApApprox innerArgApa + ) { + fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa) and + fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp) + } + + pragma[nomagic] + private predicate fwdFlowThrough( + DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, + ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa + ) { + fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa) + } + + /** + * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` + * and data might flow through the target callable and back out at `call`. + */ + pragma[nomagic] + private predicate fwdFlowIsEntered( + DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, + ParamNodeEx p, Ap ap + ) { + exists(ApApprox apa | + fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, + pragma[only_bind_into](apa)) and + PrevStage::parameterMayFlowThrough(p, apa) and + PrevStage::callMayFlowThroughRev(call) + ) + } + + pragma[nomagic] + private predicate storeStepFwd(NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2) { + fwdFlowStore(node1, ap1, tc, node2, _, _, _, _) and + ap2 = apCons(tc, ap1) and + fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _) + } + + private predicate readStepFwd(NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2) { + fwdFlowRead(ap1, c, n1, n2, _, _, _, _) and + fwdFlowConsCand(ap1, c, ap2) + } + + pragma[nomagic] + private predicate returnFlowsThrough0( + DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, + ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa + ) { + fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, + innerArgApa) + } + + pragma[nomagic] + private predicate returnFlowsThrough( + RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, + Ap ap + ) { + exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | + returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa) and + flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa) and + pos = ret.getReturnPosition() and + if allowsFieldFlow = false then ap instanceof ApNil else any() + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap + ) { + exists(ApApprox argApa | + flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), + allowsFieldFlow, argApa) and + fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa) and + returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap) and + if allowsFieldFlow = false then argAp instanceof ApNil else any() + ) + } + + pragma[nomagic] + private predicate flowIntoCallAp( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap + ) { + exists(ApApprox apa | + flowIntoCallApa(call, arg, p, allowsFieldFlow, apa) and + fwdFlow(arg, _, _, _, _, ap, apa) + ) + } + + pragma[nomagic] + private predicate flowOutOfCallAp( + DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, + Ap ap + ) { + exists(ApApprox apa | + flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa) and + fwdFlow(ret, _, _, _, _, ap, apa) and + pos = ret.getReturnPosition() + ) + } + + /** + * Holds if `node` with access path `ap` is part of a path from a source to a + * sink. + * + * The parameter `returnCtx` records whether (and how) the node must be returned + * from the enclosing callable in order to reach a sink, and if so, `returnAp` + * records the access path of the returned value. + */ + pragma[nomagic] + additional predicate revFlow( + NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap + ) { + revFlow0(node, state, returnCtx, returnAp, ap) and + fwdFlow(node, state, _, _, _, ap) + } + + pragma[nomagic] + private predicate revFlow0( + NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap + ) { + fwdFlow(node, state, _, _, _, ap) and + sinkNode(node, state) and + ( + if hasSinkCallCtx() + then returnCtx = TReturnCtxNoFlowThrough() + else returnCtx = TReturnCtxNone() + ) and + returnAp = apNone() and + ap instanceof ApNil + or + exists(NodeEx mid, FlowState state0 | + localStep(node, state, mid, state0, true, _, _) and + revFlow(mid, state0, returnCtx, returnAp, ap) + ) + or + exists(NodeEx mid, FlowState state0, ApNil nil | + fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap) and + localStep(node, pragma[only_bind_into](state), mid, state0, false, _, _) and + revFlow(mid, state0, returnCtx, returnAp, nil) and + ap instanceof ApNil + ) + or + exists(NodeEx mid | + jumpStepEx(node, mid) and + revFlow(mid, state, _, _, ap) and + returnCtx = TReturnCtxNone() and + returnAp = apNone() + ) + or + exists(NodeEx mid, ApNil nil | + fwdFlow(node, _, _, _, _, ap) and + additionalJumpStep(node, mid) and + revFlow(pragma[only_bind_into](mid), state, _, _, nil) and + returnCtx = TReturnCtxNone() and + returnAp = apNone() and + ap instanceof ApNil + ) + or + exists(NodeEx mid, FlowState state0, ApNil nil | + fwdFlow(node, _, _, _, _, ap) and + additionalJumpStateStep(node, state, mid, state0) and + revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil) and + returnCtx = TReturnCtxNone() and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp) and + revFlowConsCand(ap0, c, ap) + ) + or + // read + exists(NodeEx mid, Ap ap0 | + revFlow(mid, state, returnCtx, returnAp, ap0) and + readStepFwd(node, ap, _, mid, ap0) + ) + or + // flow into a callable + exists(ParamNodeEx p, boolean allowsFieldFlow | + revFlow(p, state, TReturnCtxNone(), returnAp, ap) and + flowIntoCallAp(_, node, p, allowsFieldFlow, ap) and + (if allowsFieldFlow = false then ap instanceof ApNil else any()) and + returnCtx = TReturnCtxNone() + ) + or + // flow through a callable + exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | + revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp) and + flowThroughIntoCall(call, node, p, _, ap, innerReturnAp) + ) + or + // flow out of a callable + exists(ReturnPosition pos | + revFlowOut(_, node, pos, state, _, _, ap) and + if returnFlowsThrough(node, pos, state, _, _, _, ap) + then ( + returnCtx = TReturnCtxMaybeFlowThrough(pos) and + returnAp = apSome(ap) + ) else ( + returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() + ) + ) + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, + ReturnCtx returnCtx, ApOption returnAp + ) { + revFlow(mid, state, returnCtx, returnAp, ap0) and + storeStepFwd(node, ap, tc, mid, ap0) and + tc.getContent() = c + } + + /** + * Holds if reverse flow with access path `tail` reaches a read of `c` + * resulting in access path `cons`. + */ + pragma[nomagic] + private predicate revFlowConsCand(Ap cons, Content c, Ap tail) { + exists(NodeEx mid, Ap tail0 | + revFlow(mid, _, _, _, tail) and + tail = pragma[only_bind_into](tail0) and + readStepFwd(_, cons, c, mid, tail0) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, + ApOption returnAp, Ap ap + ) { + exists(NodeEx out, boolean allowsFieldFlow | + revFlow(out, state, returnCtx, returnAp, ap) and + flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap) and + if allowsFieldFlow = false then ap instanceof ApNil else any() + ) + } + + pragma[nomagic] + private predicate revFlowParamToReturn( + ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap + ) { + revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), + pragma[only_bind_into](ap)) and + parameterFlowThroughAllowed(p, pos.getKind()) and + PrevStage::parameterMayFlowThrough(p, getApprox(ap)) + } + + pragma[nomagic] + private predicate revFlowThrough( + DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, + ApOption returnAp, Ap ap, Ap innerReturnAp + ) { + revFlowParamToReturn(p, state, pos, innerReturnAp, ap) and + revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. + */ + pragma[nomagic] + private predicate revFlowIsReturned( + DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap + ) { + exists(RetNodeEx ret, FlowState state, CcCall ccc | + revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap) and + returnFlowsThrough(ret, pos, state, ccc, _, _, ap) and + matchesCall(ccc, call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType + ) { + exists(Ap ap2, Content c | + PrevStage::storeStepCand(node1, _, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _) and + revFlowConsCand(ap2, c, ap1) + ) + } + + predicate readStepCand(NodeEx node1, Content c, NodeEx node2) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, _, pragma[only_bind_into](ap2)) and + readStepFwd(node1, ap1, c, node2, ap2) and + revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _) + ) + } + + additional predicate revFlow(NodeEx node, FlowState state) { revFlow(node, state, _, _, _) } + + predicate revFlow(NodeEx node, FlowState state, Ap ap) { revFlow(node, state, _, _, ap) } + + pragma[nomagic] + predicate revFlow(NodeEx node) { revFlow(node, _, _, _, _) } + + pragma[nomagic] + predicate revFlowAp(NodeEx node, Ap ap) { revFlow(node, _, _, _, ap) } + + // use an alias as a workaround for bad functionality-induced joins + pragma[nomagic] + additional predicate revFlowAlias(NodeEx node) { revFlow(node, _, _, _, _) } + + // use an alias as a workaround for bad functionality-induced joins + pragma[nomagic] + additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap) { + revFlow(node, state, ap) + } + + private predicate fwdConsCand(TypedContent tc, Ap ap) { storeStepFwd(_, ap, tc, _, _) } + + private predicate revConsCand(TypedContent tc, Ap ap) { storeStepCand(_, ap, tc, _, _) } + + private predicate validAp(Ap ap) { + revFlow(_, _, _, _, ap) and ap instanceof ApNil + or + exists(TypedContent head, Ap tail | + consCand(head, tail) and + ap = apCons(head, tail) + ) + } + + additional predicate consCand(TypedContent tc, Ap ap) { + revConsCand(tc, ap) and + validAp(ap) + } + + pragma[nomagic] + private predicate parameterFlowsThroughRev( + ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp + ) { + revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap) and + parameterFlowThroughAllowed(p, pos.getKind()) + } + + pragma[nomagic] + predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap) { + exists(ReturnPosition pos | + returnFlowsThrough(_, pos, _, _, p, ap, _) and + parameterFlowsThroughRev(p, ap, pos, _) + ) + } + + pragma[nomagic] + predicate returnMayFlowThrough(RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind) { + exists(ParamNodeEx p, ReturnPosition pos | + returnFlowsThrough(ret, pos, _, _, p, argAp, ap) and + parameterFlowsThroughRev(p, argAp, pos, ap) and + kind = pos.getKind() + ) + } + + pragma[nomagic] + private predicate revFlowThroughArg( + DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, + Ap ap + ) { + exists(ParamNodeEx p, Ap innerReturnAp | + revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp) and + flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp) + ) + } + + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call) { + exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | + revFlow(arg, state, returnCtx, returnAp, ap) and + revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap) + ) + } + + additional predicate stats( + boolean fwd, int nodes, int fields, int conscand, int states, int tuples + ) { + fwd = true and + nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap)) and + states = count(FlowState state | fwdFlow(_, state, _, _, _, _)) and + tuples = + count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | + fwdFlow(n, state, cc, summaryCtx, argAp, ap) + ) + or + fwd = false and + nodes = count(NodeEx node | revFlow(node, _, _, _, _)) and + fields = count(TypedContent f0 | consCand(f0, _)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap)) and + states = count(FlowState state | revFlow(_, state, _, _, _)) and + tuples = + count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | + revFlow(n, state, returnCtx, retAp, ap) + ) + } + /* End: Stage logic. */ + } + } + + private module BooleanCallContext { + class Cc extends boolean { + Cc() { this in [true, false] } + } + + class CcCall extends Cc { + CcCall() { this = true } + } + + /** Holds if the call context may be `call`. */ + predicate matchesCall(CcCall cc, DataFlowCall call) { any() } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccNone() { result = false } + + CcCall ccSomeCall() { result = true } - module NoLocalCallContext { class LocalCc = Unit; bindingset[node, cc] LocalCc getLocalCc(NodeEx node, Cc cc) { any() } bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSiteDispatch(call, c) - then result = TSpecificCall(call) - else result = TSomeCall() + CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c, innercc] + CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } + } + + private module Level1CallContext { + class Cc = CallContext; + + class CcCall = CallContextCall; + + pragma[inline] + predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } + + class CcNoCall = CallContextNoCall; + + Cc ccNone() { result instanceof CallContextAny } + + CcCall ccSomeCall() { result instanceof CallContextSomeCall } + + module NoLocalCallContext { + class LocalCc = Unit; + + bindingset[node, cc] + LocalCc getLocalCc(NodeEx node, Cc cc) { any() } + + bindingset[call, c, outercc] + CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + checkCallContextCall(outercc, call, c) and + if recordDataFlowCallSiteDispatch(call, c) + then result = TSpecificCall(call) + else result = TSomeCall() + } + } + + module LocalCallContext { + class LocalCc = LocalCallContext; + + bindingset[node, cc] + LocalCc getLocalCc(NodeEx node, Cc cc) { + result = + getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), + node.getEnclosingCallable()) + } + + bindingset[call, c, outercc] + CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + checkCallContextCall(outercc, call, c) and + if recordDataFlowCallSite(call, c) + then result = TSpecificCall(call) + else result = TSomeCall() + } + } + + bindingset[call, c, innercc] + CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { + checkCallContextReturn(innercc, c, call) and + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() } } - module LocalCallContext { - class LocalCc = LocalCallContext; + private module Stage2Param implements MkStage::StageParam { + private module PrevStage = Stage1; - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { - result = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - node.getEnclosingCallable()) + class Ap extends boolean { + Ap() { this in [true, false] } } - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + class ApNil extends Ap { + ApNil() { this = false } } + + bindingset[result, ap] + PrevStage::Ap getApprox(Ap ap) { any() } + + ApNil getApNil(NodeEx node) { Stage1::revFlow(node) and exists(result) } + + bindingset[tc, tail] + Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + class ApHeadContent = Unit; + + pragma[inline] + ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } + + ApHeadContent projectToHeadContent(Content c) { any() } + + class ApOption = BooleanOption; + + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + + import Level1CallContext + import NoLocalCallContext + + bindingset[node1, state1] + bindingset[node2, state2] + predicate localStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + ApNil ap, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2) and + state1 = state2 + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2) and + state1 = state2 + or + preservesValue = false and + additionalLocalStateStep(node1, state1, node2, state2) + ) and + exists(ap) and + exists(lcc) + } + + predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + predicate flowIntoCall = flowIntoCallNodeCand1/4; + + pragma[nomagic] + private predicate expectsContentCand(NodeEx node) { + exists(Content c | + PrevStage::revFlow(node) and + PrevStage::revFlowIsReadAndStored(c) and + expectsContentEx(node, c) + ) + } + + bindingset[node, state, ap] + predicate filter(NodeEx node, FlowState state, Ap ap) { + PrevStage::revFlowState(state) and + exists(ap) and + not stateBarrier(node, state) and + ( + notExpectsContent(node) + or + ap = true and + expectsContentCand(node) + ) + } + + bindingset[ap, contentType] + predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } } - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { - checkCallContextReturn(innercc, c, call) and - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() + private module Stage2 implements StageSig { + import MkStage::Stage } -} - -private module Stage2Param implements MkStage::StageParam { - private module PrevStage = Stage1; - - class Ap extends boolean { - Ap() { this in [true, false] } - } - - class ApNil extends Ap { - ApNil() { this = false } - } - - bindingset[result, ap] - PrevStage::Ap getApprox(Ap ap) { any() } - - ApNil getApNil(NodeEx node) { Stage1::revFlow(node, _) and exists(result) } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } - - class ApHeadContent = Unit; - - pragma[inline] - ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } - - ApHeadContent projectToHeadContent(Content c) { any() } - - class ApOption = BooleanOption; - - ApOption apNone() { result = TBooleanNone() } - - ApOption apSome(Ap ap) { result = TBooleanSome(ap) } - - import Level1CallContext - import NoLocalCallContext - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - ( - preservesValue = true and - localFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalStateStep(node1, state1, node2, state2, config) - ) and - exists(ap) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand1/6; - - predicate flowIntoCall = flowIntoCallNodeCand1/5; pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - expectsContentEx(node, c) - ) + private predicate flowOutOfCallNodeCand2( + DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow + ) { + flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow) and + Stage2::revFlow(node2) and + Stage2::revFlowAlias(node1) } - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - PrevStage::revFlowState(state, pragma[only_bind_into](config)) and - exists(ap) and - not stateBarrier(node, state, config) and - ( - notExpectsContent(node) - or - ap = true and - expectsContentCand(node, config) - ) + pragma[nomagic] + private predicate flowIntoCallNodeCand2( + DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow + ) { + flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow) and + Stage2::revFlow(node2) and + Stage2::revFlowAlias(node1) } - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage2 implements StageSig { - import MkStage::Stage -} - -pragma[nomagic] -private predicate flowOutOfCallNodeCand2( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -pragma[nomagic] -private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -private module LocalFlowBigStep { - /** - * A node where some checking is required, and hence the big-step relation - * is not allowed to step over. - */ - private class FlowCheckNode extends NodeEx { - FlowCheckNode() { - castNode(this.asNode()) or - clearsContentCached(this.asNode(), _) or - expectsContentCached(this.asNode(), _) + private module LocalFlowBigStep { + /** + * A node where some checking is required, and hence the big-step relation + * is not allowed to step over. + */ + private class FlowCheckNode extends NodeEx { + FlowCheckNode() { + castNode(this.asNode()) or + clearsContentCached(this.asNode(), _) or + expectsContentCached(this.asNode(), _) + } } - } - /** - * Holds if `node` can be the first node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowEntry(NodeEx node, FlowState state, Configuration config) { - Stage2::revFlow(node, state, config) and - ( - sourceNode(node, state, config) + /** + * Holds if `node` can be the first node in a maximal subsequence of local + * flow steps in a dataflow path. + */ + private predicate localFlowEntry(NodeEx node, FlowState state) { + Stage2::revFlow(node, state) and + ( + sourceNode(node, state) + or + jumpStepEx(_, node) + or + additionalJumpStep(_, node) + or + additionalJumpStateStep(_, _, node, state) + or + node instanceof ParamNodeEx + or + node.asNode() instanceof OutNodeExt + or + Stage2::storeStepCand(_, _, _, node, _) + or + Stage2::readStepCand(_, _, node) + or + node instanceof FlowCheckNode + or + exists(FlowState s | + additionalLocalStateStep(_, s, node, state) and + s != state + ) + ) + } + + /** + * Holds if `node` can be the last node in a maximal subsequence of local + * flow steps in a dataflow path. + */ + private predicate localFlowExit(NodeEx node, FlowState state) { + exists(NodeEx next | Stage2::revFlow(next, state) | + jumpStepEx(node, next) or + additionalJumpStep(node, next) or + flowIntoCallNodeCand2(_, node, next, _) or + flowOutOfCallNodeCand2(_, node, _, next, _) or + Stage2::storeStepCand(node, _, _, next, _) or + Stage2::readStepCand(node, _, next) + ) or - jumpStep(_, node, config) - or - additionalJumpStep(_, node, config) - or - additionalJumpStateStep(_, _, node, state, config) - or - node instanceof ParamNodeEx - or - node.asNode() instanceof OutNodeExt - or - Stage2::storeStepCand(_, _, _, node, _, config) - or - Stage2::readStepCand(_, _, node, config) - or - node instanceof FlowCheckNode - or - exists(FlowState s | - additionalLocalStateStep(_, s, node, state, config) and + exists(NodeEx next, FlowState s | Stage2::revFlow(next, s) | + additionalJumpStateStep(node, state, next, s) + or + additionalLocalStateStep(node, state, next, s) and s != state ) - ) - } - - /** - * Holds if `node` can be the last node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowExit(NodeEx node, FlowState state, Configuration config) { - exists(NodeEx next | Stage2::revFlow(next, state, config) | - jumpStep(node, next, config) or - additionalJumpStep(node, next, config) or - flowIntoCallNodeCand2(_, node, next, _, config) or - flowOutOfCallNodeCand2(_, node, _, next, _, config) or - Stage2::storeStepCand(node, _, _, next, _, config) or - Stage2::readStepCand(node, _, next, config) - ) - or - exists(NodeEx next, FlowState s | Stage2::revFlow(next, s, config) | - additionalJumpStateStep(node, state, next, s, config) or - additionalLocalStateStep(node, state, next, s, config) and - s != state - ) - or - Stage2::revFlow(node, state, config) and - node instanceof FlowCheckNode - or - sinkNode(node, state, config) - } + Stage2::revFlow(node, state) and + node instanceof FlowCheckNode + or + sinkNode(node, state) + } - pragma[noinline] - private predicate additionalLocalFlowStepNodeCand2( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, Configuration config - ) { - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 and - Stage2::revFlow(node1, pragma[only_bind_into](state1), false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false, - pragma[only_bind_into](config)) - or - additionalLocalStateStep(node1, state1, node2, state2, config) and - Stage2::revFlow(node1, state1, false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, state2, false, pragma[only_bind_into](config)) - } + pragma[noinline] + private predicate additionalLocalFlowStepNodeCand2( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2 + ) { + additionalLocalFlowStepNodeCand1(node1, node2) and + state1 = state2 and + Stage2::revFlow(node1, pragma[only_bind_into](state1), false) and + Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false) + or + additionalLocalStateStep(node1, state1, node2, state2) and + Stage2::revFlow(node1, state1, false) and + Stage2::revFlowAlias(node2, state2, false) + } - /** - * Holds if the local path from `node1` to `node2` is a prefix of a maximal - * subsequence of local flow steps in a dataflow path. - * - * This is the transitive closure of `[additional]localFlowStep` beginning - * at `localFlowEntry`. - */ - pragma[nomagic] - private predicate localFlowStepPlus( - NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, - Configuration config, LocalCallContext cc - ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and - ( - localFlowEntry(node1, pragma[only_bind_into](state), pragma[only_bind_into](config)) and + /** + * Holds if the local path from `node1` to `node2` is a prefix of a maximal + * subsequence of local flow steps in a dataflow path. + * + * This is the transitive closure of `[additional]localFlowStep` beginning + * at `localFlowEntry`. + */ + pragma[nomagic] + private predicate localFlowStepPlus( + NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, + LocalCallContext cc + ) { + not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and ( - localFlowStepNodeCand1(node1, node2, config) and - preservesValue = true and - t = node1.getDataFlowType() and // irrelevant dummy value - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) + localFlowEntry(node1, pragma[only_bind_into](state)) and + ( + localFlowStepNodeCand1(node1, node2) and + preservesValue = true and + t = node1.getDataFlowType() and // irrelevant dummy value + Stage2::revFlow(node2, pragma[only_bind_into](state)) + or + additionalLocalFlowStepNodeCand2(node1, state, node2, state) and + preservesValue = false and + t = node2.getDataFlowType() + ) and + node1 != node2 and + cc.relevantFor(node1.getEnclosingCallable()) and + not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) or - additionalLocalFlowStepNodeCand2(node1, state, node2, state, config) and - preservesValue = false and - t = node2.getDataFlowType() - ) and - node1 != node2 and - cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) - or - exists(NodeEx mid | - localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, - pragma[only_bind_into](config), cc) and - localFlowStepNodeCand1(mid, node2, config) and - not mid instanceof FlowCheckNode and - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) + exists(NodeEx mid | + localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, cc) and + localFlowStepNodeCand1(mid, node2) and + not mid instanceof FlowCheckNode and + Stage2::revFlow(node2, pragma[only_bind_into](state)) + ) + or + exists(NodeEx mid | + localFlowStepPlus(node1, state, mid, _, _, cc) and + additionalLocalFlowStepNodeCand2(mid, state, node2, state) and + not mid instanceof FlowCheckNode and + preservesValue = false and + t = node2.getDataFlowType() + ) ) + } + + /** + * Holds if `node1` can step to `node2` in one or more local steps and this + * path can occur as a maximal subsequence of local steps in a dataflow path. + */ + pragma[nomagic] + predicate localFlowBigStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + DataFlowType t, LocalCallContext callContext + ) { + localFlowStepPlus(node1, state1, node2, preservesValue, t, callContext) and + localFlowExit(node2, state1) and + state1 = state2 or - exists(NodeEx mid | - localFlowStepPlus(node1, state, mid, _, _, pragma[only_bind_into](config), cc) and - additionalLocalFlowStepNodeCand2(mid, state, node2, state, config) and - not mid instanceof FlowCheckNode and - preservesValue = false and - t = node2.getDataFlowType() + additionalLocalFlowStepNodeCand2(node1, state1, node2, state2) and + state1 != state2 and + preservesValue = false and + t = node2.getDataFlowType() and + callContext.relevantFor(node1.getEnclosingCallable()) and + not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | + isUnreachableInCallCached(node1.asNode(), call) or + isUnreachableInCallCached(node2.asNode(), call) ) + } + } + + private import LocalFlowBigStep + + private module Stage3Param implements MkStage::StageParam { + private module PrevStage = Stage2; + + class Ap = ApproxAccessPathFront; + + class ApNil = ApproxAccessPathFrontNil; + + PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + ApNil getApNil(NodeEx node) { + PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) + } + + bindingset[tc, tail] + Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } + + class ApHeadContent = ContentApprox; + + pragma[noinline] + ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + predicate projectToHeadContent = getContentApprox/1; + + class ApOption = ApproxAccessPathFrontOption; + + ApOption apNone() { result = TApproxAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } + + import BooleanCallContext + + predicate localStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + ApproxAccessPathFrontNil ap, LocalCc lcc + ) { + localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), _) and + exists(lcc) + } + + predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + predicate flowIntoCall = flowIntoCallNodeCand2/4; + + pragma[nomagic] + private predicate expectsContentCand(NodeEx node, Ap ap) { + exists(Content c | + PrevStage::revFlow(node) and + PrevStage::readStepCand(_, c, _) and + expectsContentEx(node, c) and + c = ap.getAHead().getContent() + ) + } + + pragma[nomagic] + private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } + + bindingset[node, state, ap] + predicate filter(NodeEx node, FlowState state, Ap ap) { + exists(state) and + (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and + ( + notExpectsContent(node) + or + expectsContentCand(node, ap) + ) + } + + bindingset[ap, contentType] + predicate typecheckStore(Ap ap, DataFlowType contentType) { + // We need to typecheck stores here, since reverse flow through a getter + // might have a different type here compared to inside the getter. + compatibleTypes(ap.getType(), contentType) + } + } + + private module Stage3 implements StageSig { + import MkStage::Stage + } + + private module Stage4Param implements MkStage::StageParam { + private module PrevStage = Stage3; + + class Ap = AccessPathFront; + + class ApNil = AccessPathFrontNil; + + PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } + + ApNil getApNil(NodeEx node) { + PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) + } + + bindingset[tc, tail] + Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + class ApHeadContent = Content; + + pragma[noinline] + ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + ApHeadContent projectToHeadContent(Content c) { result = c } + + class ApOption = AccessPathFrontOption; + + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + + import BooleanCallContext + + pragma[nomagic] + predicate localStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + ApNil ap, LocalCc lcc + ) { + localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), _) and + PrevStage::revFlow(node1, pragma[only_bind_into](state1), _) and + PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _) and + exists(lcc) + } + + pragma[nomagic] + predicate flowOutOfCall( + DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow + ) { + exists(FlowState state | + flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow) and + PrevStage::revFlow(node2, pragma[only_bind_into](state), _) and + PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _) + ) + } + + pragma[nomagic] + predicate flowIntoCall( + DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow + ) { + exists(FlowState state | + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow) and + PrevStage::revFlow(node2, pragma[only_bind_into](state), _) and + PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _) + ) + } + + pragma[nomagic] + private predicate clearSet(NodeEx node, ContentSet c) { + PrevStage::revFlow(node) and + clearsContentCached(node.asNode(), c) + } + + pragma[nomagic] + private predicate clearContent(NodeEx node, Content c) { + exists(ContentSet cs | + PrevStage::readStepCand(_, pragma[only_bind_into](c), _) and + c = cs.getAReadContent() and + clearSet(node, cs) + ) + } + + pragma[nomagic] + private predicate clear(NodeEx node, Ap ap) { clearContent(node, ap.getHead().getContent()) } + + pragma[nomagic] + private predicate expectsContentCand(NodeEx node, Ap ap) { + exists(Content c | + PrevStage::revFlow(node) and + PrevStage::readStepCand(_, c, _) and + expectsContentEx(node, c) and + c = ap.getHead().getContent() + ) + } + + pragma[nomagic] + private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } + + bindingset[node, state, ap] + predicate filter(NodeEx node, FlowState state, Ap ap) { + exists(state) and + not clear(node, ap) and + (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and + ( + notExpectsContent(node) + or + expectsContentCand(node, ap) + ) + } + + bindingset[ap, contentType] + predicate typecheckStore(Ap ap, DataFlowType contentType) { + // We need to typecheck stores here, since reverse flow through a getter + // might have a different type here compared to inside the getter. + compatibleTypes(ap.getType(), contentType) + } + } + + private module Stage4 implements StageSig { + import MkStage::Stage + } + + /** + * Holds if `argApf` is recorded as the summary context for flow reaching `node` + * and remains relevant for the following pruning stage. + */ + private predicate flowCandSummaryCtx(NodeEx node, FlowState state, AccessPathFront argApf) { + exists(AccessPathFront apf | + Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf) and + Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf) ) } /** - * Holds if `node1` can step to `node2` in one or more local steps and this - * path can occur as a maximal subsequence of local steps in a dataflow path. + * Holds if a length 2 access path approximation with the head `tc` is expected + * to be expensive. */ - pragma[nomagic] - predicate localFlowBigStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - DataFlowType t, Configuration config, LocalCallContext callContext - ) { - localFlowStepPlus(node1, state1, node2, preservesValue, t, config, callContext) and - localFlowExit(node2, state1, config) and - state1 = state2 - or - additionalLocalFlowStepNodeCand2(node1, state1, node2, state2, config) and - state1 != state2 and - preservesValue = false and - t = node2.getDataFlowType() and - callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) - } -} - -private import LocalFlowBigStep - -private module Stage3Param implements MkStage::StageParam { - private module PrevStage = Stage2; - - class Ap = ApproxAccessPathFront; - - class ApNil = ApproxAccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } - - class ApHeadContent = ContentApprox; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - predicate projectToHeadContent = getContentApprox/1; - - class ApOption = ApproxAccessPathFrontOption; - - ApOption apNone() { result = TApproxAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } - - import BooleanCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApproxAccessPathFrontNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand2/6; - - predicate flowIntoCall = flowIntoCallNodeCand2/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getAHead().getContent() + private predicate expensiveLen2unfolding(TypedContent tc) { + exists(int tails, int nodes, int apLimit, int tupleLimit | + tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf)) and + nodes = + strictcount(NodeEx n, FlowState state | + Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc)) + or + flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc)) + ) and + accessPathApproxCostLimits(apLimit, tupleLimit) and + apLimit < tails and + tupleLimit < (tails - 1) * nodes and + not tc.forceHighPrecision() ) } - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } + private newtype TAccessPathApprox = + TNil(DataFlowType t) or + TConsNil(TypedContent tc, DataFlowType t) { + Stage4::consCand(tc, TFrontNil(t)) and + not expensiveLen2unfolding(tc) + } or + TConsCons(TypedContent tc1, TypedContent tc2, int len) { + Stage4::consCand(tc1, TFrontHead(tc2)) and + len in [2 .. accessPathLimit()] and + not expensiveLen2unfolding(tc1) + } or + TCons1(TypedContent tc, int len) { + len in [1 .. accessPathLimit()] and + expensiveLen2unfolding(tc) + } - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) + /** + * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only + * the first two elements of the list and its length are tracked. If data flows + * from a source to a given node with a given `AccessPathApprox`, this indicates + * the sequence of dereference operations needed to get from the value in the node + * to the tracked object. The final type indicates the type of the tracked object. + */ + abstract private class AccessPathApprox extends TAccessPathApprox { + abstract string toString(); + + abstract TypedContent getHead(); + + abstract int len(); + + abstract DataFlowType getType(); + + abstract AccessPathFront getFront(); + + /** Gets the access path obtained by popping `head` from this path, if any. */ + abstract AccessPathApprox pop(TypedContent head); } - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} + private class AccessPathApproxNil extends AccessPathApprox, TNil { + private DataFlowType t; -private module Stage3 implements StageSig { - import MkStage::Stage -} + AccessPathApproxNil() { this = TNil(t) } -private module Stage4Param implements MkStage::StageParam { - private module PrevStage = Stage3; + override string toString() { result = concat(": " + ppReprType(t)) } - class Ap = AccessPathFront; + override TypedContent getHead() { none() } - class ApNil = AccessPathFrontNil; + override int len() { result = 0 } - PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } + override DataFlowType getType() { result = t } - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) + override AccessPathFront getFront() { result = TFrontNil(t) } + + override AccessPathApprox pop(TypedContent head) { none() } } - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + abstract private class AccessPathApproxCons extends AccessPathApprox { } - class ApHeadContent = Content; + private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { + private TypedContent tc; + private DataFlowType t; - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } + AccessPathApproxConsNil() { this = TConsNil(tc, t) } - ApHeadContent projectToHeadContent(Content c) { result = c } + override string toString() { + // The `concat` becomes "" if `ppReprType` has no result. + result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) + } - class ApOption = AccessPathFrontOption; + override TypedContent getHead() { result = tc } - ApOption apNone() { result = TAccessPathFrontNone() } + override int len() { result = 1 } - ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + override DataFlowType getType() { result = tc.getContainerType() } - import BooleanCallContext + override AccessPathFront getFront() { result = TFrontHead(tc) } - pragma[nomagic] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) and - exists(lcc) + override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } } - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } + private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { + private TypedContent tc1; + private TypedContent tc2; + private int len; - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } + AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - pragma[nomagic] - private predicate clearSet(NodeEx node, ContentSet c, Configuration config) { - PrevStage::revFlow(node, config) and - clearsContentCached(node.asNode(), c) - } + override string toString() { + if len = 2 + then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" + else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" + } - pragma[nomagic] - private predicate clearContent(NodeEx node, Content c, Configuration config) { - exists(ContentSet cs | - PrevStage::readStepCand(_, pragma[only_bind_into](c), _, pragma[only_bind_into](config)) and - c = cs.getAReadContent() and - clearSet(node, cs, pragma[only_bind_into](config)) - ) - } + override TypedContent getHead() { result = tc1 } - pragma[nomagic] - private predicate clear(NodeEx node, Ap ap, Configuration config) { - clearContent(node, ap.getHead().getContent(), config) - } + override int len() { result = len } - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getHead().getContent() - ) - } + override DataFlowType getType() { result = tc1.getContainerType() } - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } + override AccessPathFront getFront() { result = TFrontHead(tc1) } - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - not clear(node, ap, config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage4 implements StageSig { - import MkStage::Stage -} - -/** - * Holds if `argApf` is recorded as the summary context for flow reaching `node` - * and remains relevant for the following pruning stage. - */ -private predicate flowCandSummaryCtx( - NodeEx node, FlowState state, AccessPathFront argApf, Configuration config -) { - exists(AccessPathFront apf | - Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf, config) and - Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf, - config) - ) -} - -/** - * Holds if a length 2 access path approximation with the head `tc` is expected - * to be expensive. - */ -private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { - exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf, config)) and - nodes = - strictcount(NodeEx n, FlowState state | - Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - or - flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - ) and - accessPathApproxCostLimits(apLimit, tupleLimit) and - apLimit < tails and - tupleLimit < (tails - 1) * nodes and - not tc.forceHighPrecision() - ) -} - -private newtype TAccessPathApprox = - TNil(DataFlowType t) or - TConsNil(TypedContent tc, DataFlowType t) { - Stage4::consCand(tc, TFrontNil(t), _) and - not expensiveLen2unfolding(tc, _) - } or - TConsCons(TypedContent tc1, TypedContent tc2, int len) { - Stage4::consCand(tc1, TFrontHead(tc2), _) and - len in [2 .. accessPathLimit()] and - not expensiveLen2unfolding(tc1, _) - } or - TCons1(TypedContent tc, int len) { - len in [1 .. accessPathLimit()] and - expensiveLen2unfolding(tc, _) - } - -/** - * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only - * the first two elements of the list and its length are tracked. If data flows - * from a source to a given node with a given `AccessPathApprox`, this indicates - * the sequence of dereference operations needed to get from the value in the node - * to the tracked object. The final type indicates the type of the tracked object. - */ -abstract private class AccessPathApprox extends TAccessPathApprox { - abstract string toString(); - - abstract TypedContent getHead(); - - abstract int len(); - - abstract DataFlowType getType(); - - abstract AccessPathFront getFront(); - - /** Gets the access path obtained by popping `head` from this path, if any. */ - abstract AccessPathApprox pop(TypedContent head); -} - -private class AccessPathApproxNil extends AccessPathApprox, TNil { - private DataFlowType t; - - AccessPathApproxNil() { this = TNil(t) } - - override string toString() { result = concat(": " + ppReprType(t)) } - - override TypedContent getHead() { none() } - - override int len() { result = 0 } - - override DataFlowType getType() { result = t } - - override AccessPathFront getFront() { result = TFrontNil(t) } - - override AccessPathApprox pop(TypedContent head) { none() } -} - -abstract private class AccessPathApproxCons extends AccessPathApprox { } - -private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { - private TypedContent tc; - private DataFlowType t; - - AccessPathApproxConsNil() { this = TConsNil(tc, t) } - - override string toString() { - // The `concat` becomes "" if `ppReprType` has no result. - result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) - } - - override TypedContent getHead() { result = tc } - - override int len() { result = 1 } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } -} - -private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { - private TypedContent tc1; - private TypedContent tc2; - private int len; - - AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - - override string toString() { - if len = 2 - then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" - else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc1 } - - override int len() { result = len } - - override DataFlowType getType() { result = tc1.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc1) } - - override AccessPathApprox pop(TypedContent head) { - head = tc1 and - ( - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - } -} - -private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { - private TypedContent tc; - private int len; - - AccessPathApproxCons1() { this = TCons1(tc, len) } - - override string toString() { - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc } - - override int len() { result = len } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { - head = tc and - ( - exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2), _) | + override AccessPathApprox pop(TypedContent head) { + head = tc1 and + ( result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -2763,1561 +2471,585 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or result = TCons1(tc2, len - 1) ) - or - exists(DataFlowType t | - len = 1 and - Stage4::consCand(tc, TFrontNil(t), _) and - result = TNil(t) + } + } + + private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { + private TypedContent tc; + private int len; + + AccessPathApproxCons1() { this = TCons1(tc, len) } + + override string toString() { + if len = 1 + then result = "[" + tc.toString() + "]" + else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" + } + + override TypedContent getHead() { result = tc } + + override int len() { result = len } + + override DataFlowType getType() { result = tc.getContainerType() } + + override AccessPathFront getFront() { result = TFrontHead(tc) } + + override AccessPathApprox pop(TypedContent head) { + head = tc and + ( + exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2)) | + result = TConsCons(tc2, _, len - 1) + or + len = 2 and + result = TConsNil(tc2, _) + or + result = TCons1(tc2, len - 1) + ) + or + exists(DataFlowType t | + len = 1 and + Stage4::consCand(tc, TFrontNil(t)) and + result = TNil(t) + ) ) - ) + } } -} -/** Gets the access path obtained by popping `tc` from `ap`, if any. */ -private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } + /** Gets the access path obtained by popping `tc` from `ap`, if any. */ + private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } -/** Gets the access path obtained by pushing `tc` onto `ap`. */ -private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } + /** Gets the access path obtained by pushing `tc` onto `ap`. */ + private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } -private newtype TAccessPathApproxOption = - TAccessPathApproxNone() or - TAccessPathApproxSome(AccessPathApprox apa) + private newtype TAccessPathApproxOption = + TAccessPathApproxNone() or + TAccessPathApproxSome(AccessPathApprox apa) -private class AccessPathApproxOption extends TAccessPathApproxOption { - string toString() { - this = TAccessPathApproxNone() and result = "" - or - this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) + private class AccessPathApproxOption extends TAccessPathApproxOption { + string toString() { + this = TAccessPathApproxNone() and result = "" + or + this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) + } } -} -private module Stage5Param implements MkStage::StageParam { - private module PrevStage = Stage4; + private module Stage5Param implements MkStage::StageParam { + private module PrevStage = Stage4; - class Ap = AccessPathApprox; + class Ap = AccessPathApprox; - class ApNil = AccessPathApproxNil; + class ApNil = AccessPathApproxNil; + + pragma[nomagic] + PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } + + ApNil getApNil(NodeEx node) { + PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) + } + + bindingset[tc, tail] + Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + class ApHeadContent = Content; + + pragma[noinline] + ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + ApHeadContent projectToHeadContent(Content c) { result = c } + + class ApOption = AccessPathApproxOption; + + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + + import Level1CallContext + import LocalCallContext + + predicate localStep( + NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, + ApNil ap, LocalCc lcc + ) { + localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), lcc) and + PrevStage::revFlow(node1, pragma[only_bind_into](state1), _) and + PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _) + } + + pragma[nomagic] + predicate flowOutOfCall( + DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow + ) { + exists(FlowState state | + flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow) and + PrevStage::revFlow(node2, pragma[only_bind_into](state), _) and + PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _) + ) + } + + pragma[nomagic] + predicate flowIntoCall( + DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow + ) { + exists(FlowState state | + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow) and + PrevStage::revFlow(node2, pragma[only_bind_into](state), _) and + PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _) + ) + } + + bindingset[node, state, ap] + predicate filter(NodeEx node, FlowState state, Ap ap) { any() } + + // Type checking is not necessary here as it has already been done in stage 3. + bindingset[ap, contentType] + predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + } + + private module Stage5 = MkStage::Stage; pragma[nomagic] - PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathApproxOption; - - ApOption apNone() { result = TAccessPathApproxNone() } - - ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } - - import Level1CallContext - import LocalCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc + private predicate nodeMayUseSummary0( + NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, lcc) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) + exists(AccessPathApprox apa0 | + Stage5::parameterMayFlowThrough(p, _) and + Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0) and + Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), + TAccessPathApproxSome(apa), apa0) ) } pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) + private predicate nodeMayUseSummary(NodeEx n, FlowState state, AccessPathApprox apa) { + exists(ParamNodeEx p | + Stage5::parameterMayFlowThrough(p, apa) and + nodeMayUseSummary0(n, p, state, apa) ) } - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { any() } + private newtype TSummaryCtx = + TSummaryCtxNone() or + TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { + Stage5::parameterMayFlowThrough(p, ap.getApprox()) and + Stage5::revFlow(p, state, _) + } - // Type checking is not necessary here as it has already been done in stage 3. - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} + /** + * A context for generating flow summaries. This represents flow entry through + * a specific parameter with an access path of a specific shape. + * + * Summaries are only created for parameters that may flow through. + */ + abstract private class SummaryCtx extends TSummaryCtx { + abstract string toString(); + } -private module Stage5 = MkStage::Stage; + /** A summary context from which no flow summary can be generated. */ + private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { + override string toString() { result = "" } + } -bindingset[conf, result] -private Configuration unbindConf(Configuration conf) { - exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c)) -} + /** A summary context from which a flow summary can be generated. */ + private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { + private ParamNodeEx p; + private FlowState s; + private AccessPath ap; -pragma[nomagic] -private predicate nodeMayUseSummary0( - NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(AccessPathApprox apa0 | - Stage5::parameterMayFlowThrough(p, _, _) and - Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0, config) and - Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), - TAccessPathApproxSome(apa), apa0, config) - ) -} + SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } -pragma[nomagic] -private predicate nodeMayUseSummary( - NodeEx n, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::parameterMayFlowThrough(p, apa, config) and - nodeMayUseSummary0(n, p, state, apa, config) - ) -} + ParameterPosition getParameterPos() { p.isParameterOf(_, result) } -private newtype TSummaryCtx = - TSummaryCtxNone() or - TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { - exists(Configuration config | - Stage5::parameterMayFlowThrough(p, ap.getApprox(), config) and - Stage5::revFlow(p, state, _, config) + ParamNodeEx getParamNode() { result = p } + + override string toString() { result = p + ": " + ap } + + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * Gets the number of length 2 access path approximations that correspond to `apa`. + */ + private int count1to2unfold(AccessPathApproxCons1 apa) { + exists(TypedContent tc, int len | + tc = apa.getHead() and + len = apa.len() and + result = + strictcount(AccessPathFront apf | + Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1)) + ) ) } -/** - * A context for generating flow summaries. This represents flow entry through - * a specific parameter with an access path of a specific shape. - * - * Summaries are only created for parameters that may flow through. - */ -abstract private class SummaryCtx extends TSummaryCtx { - abstract string toString(); -} - -/** A summary context from which no flow summary can be generated. */ -private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { - override string toString() { result = "" } -} - -/** A summary context from which a flow summary can be generated. */ -private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParamNodeEx p; - private FlowState s; - private AccessPath ap; - - SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } - - ParameterPosition getParameterPos() { p.isParameterOf(_, result) } - - ParamNodeEx getParamNode() { result = p } - - override string toString() { result = p + ": " + ap } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** - * Gets the number of length 2 access path approximations that correspond to `apa`. - */ -private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { - exists(TypedContent tc, int len | - tc = apa.getHead() and - len = apa.len() and + private int countNodesUsingAccessPath(AccessPathApprox apa) { result = - strictcount(AccessPathFront apf | - Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), - config) + strictcount(NodeEx n, FlowState state | + Stage5::revFlow(n, state, apa) or nodeMayUseSummary(n, state, apa) ) - ) -} + } -private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = - strictcount(NodeEx n, FlowState state | - Stage5::revFlow(n, state, apa, config) or nodeMayUseSummary(n, state, apa, config) - ) -} - -/** - * Holds if a length 2 access path approximation matching `apa` is expected - * to be expensive. - */ -private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = count1to2unfold(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - apLimit < aps and - tupleLimit < (aps - 1) * nodes - ) -} - -private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { - exists(TypedContent head | - apa.pop(head) = result and - Stage5::consCand(head, result, config) - ) -} - -/** - * Holds with `unfold = false` if a precise head-tail representation of `apa` is - * expected to be expensive. Holds with `unfold = true` otherwise. - */ -private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { - if apa.getHead().forceHighPrecision() - then unfold = true - else + /** + * Holds if a length 2 access path approximation matching `apa` is expected + * to be expensive. + */ + private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa) { exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = countPotentialAps(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and + aps = count1to2unfold(apa) and + nodes = countNodesUsingAccessPath(apa) and accessPathCostLimits(apLimit, tupleLimit) and - if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true - ) -} - -/** - * Gets the number of `AccessPath`s that correspond to `apa`. - */ -pragma[assume_small_delta] -private int countAps(AccessPathApprox apa, Configuration config) { - evalUnfold(apa, false, config) and - result = 1 and - (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) - or - evalUnfold(apa, false, config) and - result = count1to2unfold(apa, config) and - not expensiveLen1to2unfolding(apa, config) - or - evalUnfold(apa, true, config) and - result = countPotentialAps(apa, config) -} - -/** - * Gets the number of `AccessPath`s that would correspond to `apa` assuming - * that it is expanded to a precise head-tail representation. - */ -language[monotonicAggregates] -pragma[assume_small_delta] -private int countPotentialAps(AccessPathApprox apa, Configuration config) { - apa instanceof AccessPathApproxNil and result = 1 - or - result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) -} - -private newtype TAccessPath = - TAccessPathNil(DataFlowType t) or - TAccessPathCons(TypedContent head, AccessPath tail) { - exists(AccessPathApproxCons apa | - not evalUnfold(apa, false, _) and - head = apa.getHead() and - tail.getApprox() = getATail(apa, _) - ) - } or - TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - not expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head1 = apa.getHead() and - head2 = getATail(apa, _).getHead() - ) - } or - TAccessPathCons1(TypedContent head, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head = apa.getHead() + apLimit < aps and + tupleLimit < (aps - 1) * nodes ) } -private newtype TPathNode = - pragma[assume_small_delta] - TPathNodeMid( - NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config - ) { - // A PathNode is introduced by a source ... - Stage5::revFlow(node, state, config) and - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - or - // ... or a step from an existing PathNode to another node. - exists(PathNodeMid mid | - pathStep(mid, node, state, cc, sc, ap) and - pragma[only_bind_into](config) = mid.getConfiguration() and - Stage5::revFlow(node, state, ap.getApprox(), pragma[only_bind_into](config)) - ) - } or - TPathNodeSink(NodeEx node, FlowState state, Configuration config) { - exists(PathNodeMid sink | - sink.isAtSink() and - node = sink.getNodeEx() and - state = sink.getState() and - config = sink.getConfiguration() - ) - } or - TPathNodeSourceGroup(string sourceGroup, Configuration config) { - exists(PathNodeImpl source | - sourceGroup = source.getSourceGroup() and - config = source.getConfiguration() - ) - } or - TPathNodeSinkGroup(string sinkGroup, Configuration config) { - exists(PathNodeSink sink | - sinkGroup = sink.getSinkGroup() and - config = sink.getConfiguration() + private AccessPathApprox getATail(AccessPathApprox apa) { + exists(TypedContent head | + apa.pop(head) = result and + Stage5::consCand(head, result) ) } -/** - * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a - * source to a given node with a given `AccessPath`, this indicates the sequence - * of dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ -private class AccessPath extends TAccessPath { - /** Gets the head of this access path, if any. */ - abstract TypedContent getHead(); - - /** Gets the tail of this access path, if any. */ - abstract AccessPath getTail(); - - /** Gets the front of this access path. */ - abstract AccessPathFront getFront(); - - /** Gets the approximation of this access path. */ - abstract AccessPathApprox getApprox(); - - /** Gets the length of this access path. */ - abstract int length(); - - /** Gets a textual representation of this access path. */ - abstract string toString(); - - /** Gets the access path obtained by popping `tc` from this access path, if any. */ - final AccessPath pop(TypedContent tc) { - result = this.getTail() and - tc = this.getHead() - } - - /** Gets the access path obtained by pushing `tc` onto this access path. */ - final AccessPath push(TypedContent tc) { this = result.pop(tc) } -} - -private class AccessPathNil extends AccessPath, TAccessPathNil { - private DataFlowType t; - - AccessPathNil() { this = TAccessPathNil(t) } - - DataFlowType getType() { result = t } - - override TypedContent getHead() { none() } - - override AccessPath getTail() { none() } - - override AccessPathFrontNil getFront() { result = TFrontNil(t) } - - override AccessPathApproxNil getApprox() { result = TNil(t) } - - override int length() { result = 0 } - - override string toString() { result = concat(": " + ppReprType(t)) } -} - -private class AccessPathCons extends AccessPath, TAccessPathCons { - private TypedContent head; - private AccessPath tail; - - AccessPathCons() { this = TAccessPathCons(head, tail) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { result = tail } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - pragma[assume_small_delta] - override AccessPathApproxCons getApprox() { - result = TConsNil(head, tail.(AccessPathNil).getType()) - or - result = TConsCons(head, tail.getHead(), this.length()) - or - result = TCons1(head, this.length()) - } - - pragma[assume_small_delta] - override int length() { result = 1 + tail.length() } - - private string toStringImpl(boolean needsSuffix) { - exists(DataFlowType t | - tail = TAccessPathNil(t) and - needsSuffix = false and - result = head.toString() + "]" + concat(" : " + ppReprType(t)) - ) - or - result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) - or - exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | - result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true - or - result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false - ) - or - exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | - result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true - or - result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false - ) - } - - override string toString() { - result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" - or - result = "[" + this.toStringImpl(false) - } -} - -private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { - private TypedContent head1; - private TypedContent head2; - private int len; - - AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } - - override TypedContent getHead() { result = head1 } - - override AccessPath getTail() { - Stage5::consCand(head1, result.getApprox(), _) and - result.getHead() = head2 and - result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head1) } - - override AccessPathApproxCons getApprox() { - result = TConsCons(head1, head2, len) or - result = TCons1(head1, len) - } - - override int length() { result = len } - - override string toString() { - if len = 2 - then result = "[" + head1.toString() + ", " + head2.toString() + "]" + /** + * Holds with `unfold = false` if a precise head-tail representation of `apa` is + * expected to be expensive. Holds with `unfold = true` otherwise. + */ + private predicate evalUnfold(AccessPathApprox apa, boolean unfold) { + if apa.getHead().forceHighPrecision() + then unfold = true else - result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" - } -} - -private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { - private TypedContent head; - private int len; - - AccessPathCons1() { this = TAccessPathCons1(head, len) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { - Stage5::consCand(head, result.getApprox(), _) and result.length() = len - 1 + exists(int aps, int nodes, int apLimit, int tupleLimit | + aps = countPotentialAps(apa) and + nodes = countNodesUsingAccessPath(apa) and + accessPathCostLimits(apLimit, tupleLimit) and + if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true + ) } - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - - override int length() { result = len } - - override string toString() { - if len = 1 - then result = "[" + head.toString() + "]" - else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" - } -} - -abstract private class PathNodeImpl extends TPathNode { - /** Gets the `FlowState` of this node. */ - abstract FlowState getState(); - - /** Gets the associated configuration. */ - abstract Configuration getConfiguration(); - - /** Holds if this node is a source. */ - abstract predicate isSource(); - - abstract PathNodeImpl getASuccessorImpl(); - - private PathNodeImpl getASuccessorIfHidden() { - this.isHidden() and - result = this.getASuccessorImpl() - } - - pragma[nomagic] - private PathNodeImpl getANonHiddenSuccessor0() { - result = this.getASuccessorIfHidden*() and - not result.isHidden() - } - - final PathNodeImpl getANonHiddenSuccessor() { - result = this.getASuccessorImpl().getANonHiddenSuccessor0() and - not this.isHidden() - } - - abstract NodeEx getNodeEx(); - - predicate isHidden() { - not this.getConfiguration().includeHiddenNodes() and - ( - hiddenNode(this.getNodeEx().asNode()) and - not this.isSource() and - not this instanceof PathNodeSink - or - this.getNodeEx() instanceof TNodeImplicitRead - ) - } - - string getSourceGroup() { - this.isSource() and - this.getConfiguration().sourceGrouping(this.getNodeEx().asNode(), result) - } - - predicate isFlowSource() { - this.isSource() and not exists(this.getSourceGroup()) + /** + * Gets the number of `AccessPath`s that correspond to `apa`. + */ + pragma[assume_small_delta] + private int countAps(AccessPathApprox apa) { + evalUnfold(apa, false) and + result = 1 and + (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa)) or - this instanceof PathNodeSourceGroup - } - - predicate isFlowSink() { - this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or - this instanceof PathNodeSinkGroup - } - - private string ppAp() { - this instanceof PathNodeSink and result = "" + evalUnfold(apa, false) and + result = count1to2unfold(apa) and + not expensiveLen1to2unfolding(apa) or - exists(string s | s = this.(PathNodeMid).getAp().toString() | - if s = "" then result = "" else result = " " + s - ) + evalUnfold(apa, true) and + result = countPotentialAps(apa) } - private string ppCtx() { - this instanceof PathNodeSink and result = "" + /** + * Gets the number of `AccessPath`s that would correspond to `apa` assuming + * that it is expanded to a precise head-tail representation. + */ + language[monotonicAggregates] + pragma[assume_small_delta] + private int countPotentialAps(AccessPathApprox apa) { + apa instanceof AccessPathApproxNil and result = 1 or - result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + result = strictsum(AccessPathApprox tail | tail = getATail(apa) | countAps(tail)) } - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** Holds if `n` can reach a sink. */ -private predicate directReach(PathNodeImpl n) { - n instanceof PathNodeSink or - n instanceof PathNodeSinkGroup or - directReach(n.getANonHiddenSuccessor()) -} - -/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ -private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } - -/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { - n1.getANonHiddenSuccessor() = n2 and directReach(n2) -} - -private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) - -/** - * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. - * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. - */ -class PathNode instanceof PathNodeImpl { - PathNode() { reach(this) } - - /** Gets a textual representation of this element. */ - final string toString() { result = super.toString() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - final string toStringWithContext() { result = super.toStringWithContext() } - - /** - * 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/). - */ - final predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - /** Gets the underlying `Node`. */ - final Node getNode() { super.getNodeEx().projectToNode() = result } - - /** Gets the `FlowState` of this node. */ - final FlowState getState() { result = super.getState() } - - /** Gets the associated configuration. */ - final Configuration getConfiguration() { result = super.getConfiguration() } - - /** Gets a successor of this node, if any. */ - final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } - - /** Holds if this node is a source. */ - final predicate isSource() { super.isSource() } - - /** Holds if this node is a grouping of source nodes. */ - final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group, _) } - - /** Holds if this node is a grouping of sink nodes. */ - final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group, _) } -} - -/** - * Provides the query predicates needed to include a graph in a path-problem query. - */ -module PathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } - - /** Holds if `n` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode n, string key, string val) { - key = "semmle.label" and val = n.toString() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { - Subpaths::subpaths(arg, par, ret, out) - } -} - -/** - * An intermediate flow graph node. This is a triple consisting of a `Node`, - * a `CallContext`, and a `Configuration`. - */ -private class PathNodeMid extends PathNodeImpl, TPathNodeMid { - NodeEx node; - FlowState state; - CallContext cc; - SummaryCtx sc; - AccessPath ap; - Configuration config; - - PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - SummaryCtx getSummaryCtx() { result = sc } - - AccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - private PathNodeMid getSuccMid() { - pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx(), result.getAp()) and - result.getConfiguration() = unbindConf(this.getConfiguration()) - } - - override PathNodeImpl getASuccessorImpl() { - // an intermediate step to another intermediate node - result = this.getSuccMid() - or - // a final step to a sink - result = this.getSuccMid().projectToSink() - } - - override predicate isSource() { - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - } - - predicate isAtSink() { - sinkNode(node, state, config) and - ap instanceof AccessPathNil and - if hasSinkCallCtx(config) - then - // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` - // is exactly what we need to check. This also implies - // `sc instanceof SummaryCtxNone`. - // For `FeatureEqualSourceSinkCallContext` the initial call context was - // set to `CallContextSomeCall` and jumps are disallowed, so - // `cc instanceof CallContextNoCall` never holds. On the other hand, - // in this case there's never any need to enter a call except to identify - // a summary, so the condition in `pathIntoCallable` enforces this, which - // means that `sc instanceof SummaryCtxNone` holds if and only if we are - // in the call context of the source. - sc instanceof SummaryCtxNone or - cc instanceof CallContextNoCall - else any() - } - - PathNodeSink projectToSink() { - this.isAtSink() and - result.getNodeEx() = node and - result.getState() = state and - result.getConfiguration() = unbindConf(config) - } -} - -/** - * A flow graph node corresponding to a sink. This is disjoint from the - * intermediate nodes in order to uniquely correspond to a given sink by - * excluding the `CallContext`. - */ -private class PathNodeSink extends PathNodeImpl, TPathNodeSink { - NodeEx node; - FlowState state; - Configuration config; - - PathNodeSink() { this = TPathNodeSink(node, state, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result = TPathNodeSinkGroup(this.getSinkGroup(), config) - } - - override predicate isSource() { sourceNode(node, state, config) } - - string getSinkGroup() { config.sinkGrouping(node.asNode(), result) } -} - -private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { - string sourceGroup; - Configuration config; - - PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result.getSourceGroup() = sourceGroup and - result.getConfiguration() = config - } - - override predicate isSource() { none() } - - override string toString() { result = sourceGroup } - - override 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 - } -} - -private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { - string sinkGroup; - Configuration config; - - PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { none() } - - override predicate isSource() { none() } - - override string toString() { result = sinkGroup } - - override 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 - } -} - -private predicate pathNode( - PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, - Configuration conf, LocalCallContext localCC -) { - midnode = mid.getNodeEx() and - state = mid.getState() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - midnode.getEnclosingCallable()) and - ap = mid.getAp() -} - -/** - * Holds if data may flow from `mid` to `node`. The last step in or out of - * a callable is recorded by `cc`. - */ -pragma[assume_small_delta] -pragma[nomagic] -private predicate pathStep( - PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap -) { - exists(NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC | - pathNode(mid, midnode, state0, cc, sc, ap, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, true, _, conf, localCC) - ) - or - exists( - AccessPath ap0, NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC - | - pathNode(mid, midnode, state0, cc, sc, ap0, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), conf, - localCC) and - ap0 instanceof AccessPathNil - ) - or - jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - ap = mid.getAp() - or - additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, mid.getConfiguration()) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - pathIntoCallable(mid, node, state, _, cc, sc, _, _) and ap = mid.getAp() - or - pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone - or - pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() -} - -pragma[nomagic] -private predicate pathReadStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - tc = ap0.getHead() and - Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -pragma[nomagic] -private predicate pathStoreStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -private predicate pathOutOfCallable0( - PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa, - Configuration config -) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - apa = mid.getAp().getApprox() and - config = mid.getConfiguration() -} - -pragma[nomagic] -private predicate pathOutOfCallable1( - PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPathApprox apa, Configuration config -) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - pathOutOfCallable0(mid, pos, state, innercc, apa, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) -} - -pragma[noinline] -private NodeEx getAnOutNodeFlow( - ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config -) { - result.asNode() = kind.getAnOutNode(call) and - Stage5::revFlow(result, _, apa, config) -} - -/** - * Holds if data may flow from `mid` to `out`. The last step of this path - * is a return from a callable and is recorded by `cc`, if needed. - */ -pragma[noinline] -private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { - exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | - pathOutOfCallable1(mid, call, kind, state, cc, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. - */ -pragma[noinline] -private predicate pathIntoArg( - PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(ArgNodeEx arg, ArgumentPosition apos | - pathNode(mid, arg, state, cc, _, ap, config, _) and - arg.asNode().(ArgNode).argumentOf(call, apos) and - apa = ap.getApprox() and - parameterMatch(ppos, apos) - ) -} - -pragma[nomagic] -private predicate parameterCand( - DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::revFlow(p, _, apa, config) and - p.isParameterOf(callable, pos) - ) -} - -pragma[nomagic] -private predicate pathIntoCallable0( - PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, AccessPath ap, Configuration config -) { - exists(AccessPathApprox apa | - pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - callable = resolveCall(call, outercc) and - parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa), - pragma[only_bind_into](config)) - ) -} - -/** - * Holds if data may flow from `mid` to `p` through `call`. The contexts - * before and after entering the callable are `outercc` and `innercc`, - * respectively. - */ -pragma[nomagic] -private predicate pathIntoCallable( - PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, - SummaryCtx sc, DataFlowCall call, Configuration config -) { - exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | - pathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - ( - sc = TSummaryCtxSome(p, state, ap) - or - not exists(TSummaryCtxSome(p, state, ap)) and - sc = TSummaryCtxNone() and - // When the call contexts of source and sink needs to match then there's - // never any reason to enter a callable except to find a summary. See also - // the comment in `PathNodeMid::isAtSink`. - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) -} - -/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ -pragma[nomagic] -private predicate paramFlowsThrough( - ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, - AccessPathApprox apa, Configuration config -) { - exists(RetNodeEx ret | - pathNode(_, ret, state, cc, sc, ap, config, _) and - kind = ret.getKind() and - apa = ap.getApprox() and - parameterFlowThroughAllowed(sc.getParamNode(), kind) - ) -} - -pragma[nomagic] -private predicate pathThroughCallable0( - DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(CallContext innercc, SummaryCtx sc | - pathIntoCallable(mid, _, _, cc, innercc, sc, call, config) and - paramFlowsThrough(kind, state, innercc, sc, ap, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` through a callable to the node `out`. - * The context `cc` is restored to its value prior to entering the callable. - */ -pragma[noinline] -private predicate pathThroughCallable( - PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap -) { - exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config | - pathThroughCallable0(call, mid, kind, state, cc, ap, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -private module Subpaths { - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths01( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - exists(Configuration config | - pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and - pathIntoCallable(arg, par, _, _, innercc, sc, _, config) and - paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, - pragma[only_bind_into](apout), _, unbindConf(config)) and - not arg.isHidden() - ) - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `sout`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths02( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and - out.asNode() = kind.getAnOutNode(_) - } - - pragma[nomagic] - private Configuration getPathNodeConf(PathNodeImpl n) { result = n.getConfiguration() } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple. - */ - pragma[nomagic] - private predicate subpaths03( - PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, AccessPath apout - ) { - exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | - subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and - pathNode(ret, retnode, sout, innercc, sc, apout, unbindConf(getPathNodeConf(arg)), _) and - kind = retnode.getKind() - ) - } - - private PathNodeImpl localStepToHidden(PathNodeImpl n) { - n.getASuccessorImpl() = result and - result.isHidden() and - exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | - localFlowBigStep(n1, _, n2, _, _, _, _, _) or - store(n1, _, n2, _, _) or - readSet(n1, _, n2, _) - ) - } - - pragma[nomagic] - private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { - succ = pred.getANonHiddenSuccessor() and - succNode = succ.getNodeEx() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { - exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | - pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and - subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and - hasSuccessor(pragma[only_bind_into](arg), par, p) and - not ret.isHidden() and - pathNode(out0, o, sout, _, _, apout, _, _) - | - out = out0 or out = out0.projectToSink() - ) - } - - /** - * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. - */ - predicate retReach(PathNodeImpl n) { - exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) - or - exists(PathNodeImpl mid | - retReach(mid) and - n.getANonHiddenSuccessor() = mid and - not subpaths(_, mid, _, _) - ) - } -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -private predicate hasFlowPath( - PathNodeImpl flowsource, PathNodeImpl flowsink, Configuration configuration -) { - flowsource.isFlowSource() and - flowsource.getConfiguration() = configuration and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.isFlowSink() -} - -private predicate flowsTo( - PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink, - Configuration configuration -) { - flowsource.isSource() and - flowsource.getConfiguration() = configuration and - flowsource.getNodeEx().asNode() = source and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.getNodeEx().asNode() = sink -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -predicate flowsTo(Node source, Node sink, Configuration configuration) { - flowsTo(_, _, source, sink, configuration) -} - -private predicate finalStats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples -) { - fwd = true and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and - tuples = count(PathNodeImpl pn) - or - fwd = false and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and - tuples = count(PathNode pn) -} - -/** - * INTERNAL: Only for debugging. - * - * Calculates per-stage metrics for data flow. - */ -predicate stageStats( - int n, string stage, int nodes, int fields, int conscand, int states, int tuples, - Configuration config -) { - stage = "1 Fwd" and - n = 10 and - Stage1::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "1 Rev" and - n = 15 and - Stage1::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Fwd" and - n = 20 and - Stage2::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Rev" and - n = 25 and - Stage2::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Fwd" and - n = 30 and - Stage3::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Rev" and - n = 35 and - Stage3::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Fwd" and - n = 40 and - Stage4::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Rev" and - n = 45 and - Stage4::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Fwd" and - n = 50 and - Stage5::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Rev" and - n = 55 and - Stage5::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) - or - stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) -} - -private module FlowExploration { - private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - jumpStep(node1, node2, config) - or - additionalJumpStep(node1, node2, config) - or - additionalJumpStateStep(node1, _, node2, _, config) - or - // flow into callable - viableParamArgEx(_, node2, node1) - or - // flow out of a callable - viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) - | - c1 = node1.getEnclosingCallable() and - c2 = node2.getEnclosingCallable() and - c1 != c2 - ) - } - - private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSource(n) or config.isSource(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSrc(mid, config) and callableStep(mid, c, config) - ) - } - - private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSink(n) or config.isSink(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSink(mid, config) and callableStep(c, mid, config) - ) - } - - private newtype TCallableExt = - TCallable(DataFlowCallable c, Configuration config) { - interestingCallableSrc(c, config) or - interestingCallableSink(c, config) + private newtype TAccessPath = + TAccessPathNil(DataFlowType t) or + TAccessPathCons(TypedContent head, AccessPath tail) { + exists(AccessPathApproxCons apa | + not evalUnfold(apa, false) and + head = apa.getHead() and + tail.getApprox() = getATail(apa) + ) } or - TCallableSrc() or - TCallableSink() + TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { + exists(AccessPathApproxCons apa | + evalUnfold(apa, false) and + not expensiveLen1to2unfolding(apa) and + apa.len() = len and + head1 = apa.getHead() and + head2 = getATail(apa).getHead() + ) + } or + TAccessPathCons1(TypedContent head, int len) { + exists(AccessPathApproxCons apa | + evalUnfold(apa, false) and + expensiveLen1to2unfolding(apa) and + apa.len() = len and + head = apa.getHead() + ) + } - private predicate callableExtSrc(TCallableSrc src) { any() } - - private predicate callableExtSink(TCallableSink sink) { any() } - - private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { - exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | - callableStep(c1, c2, config) and - ce1 = TCallable(c1, pragma[only_bind_into](config)) and - ce2 = TCallable(c2, pragma[only_bind_into](config)) - ) - or - exists(Node n, Configuration config | - ce1 = TCallableSrc() and - (config.isSource(n) or config.isSource(n, _)) and - ce2 = TCallable(getNodeEnclosingCallable(n), config) - ) - or - exists(Node n, Configuration config | - ce2 = TCallableSink() and - (config.isSink(n) or config.isSink(n, _)) and - ce1 = TCallable(getNodeEnclosingCallable(n), config) - ) - } - - private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { - callableExtStepFwd(ce2, ce1) - } - - private int distSrcExt(TCallableExt c) = - shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) - - private int distSinkExt(TCallableExt c) = - shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) - - private int distSrc(DataFlowCallable c, Configuration config) { - result = distSrcExt(TCallable(c, config)) - 1 - } - - private int distSink(DataFlowCallable c, Configuration config) { - result = distSinkExt(TCallable(c, config)) - 1 - } - - private newtype TPartialAccessPath = - TPartialNil(DataFlowType t) or - TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } + private newtype TPathNode = + pragma[assume_small_delta] + TPathNodeMid(NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap) { + // A PathNode is introduced by a source ... + Stage5::revFlow(node, state) and + sourceNode(node, state) and + ( + if hasSourceCallCtx() + then cc instanceof CallContextSomeCall + else cc instanceof CallContextAny + ) and + sc instanceof SummaryCtxNone and + ap = TAccessPathNil(node.getDataFlowType()) + or + // ... or a step from an existing PathNode to another node. + pathStep(_, node, state, cc, sc, ap) and + Stage5::revFlow(node, state, ap.getApprox()) + } or + TPathNodeSink(NodeEx node, FlowState state) { + exists(PathNodeMid sink | + sink.isAtSink() and + node = sink.getNodeEx() and + state = sink.getState() + ) + } or + TPathNodeSourceGroup(string sourceGroup) { + exists(PathNodeImpl source | sourceGroup = source.getSourceGroup()) + } or + TPathNodeSinkGroup(string sinkGroup) { + exists(PathNodeSink sink | sinkGroup = sink.getSinkGroup()) + } /** - * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first - * element of the list and its length are tracked. If data flows from a source to - * a given node with a given `AccessPath`, this indicates the sequence of - * dereference operations needed to get from the value in the node to the + * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a + * source to a given node with a given `AccessPath`, this indicates the sequence + * of dereference operations needed to get from the value in the node to the * tracked object. The final type indicates the type of the tracked object. */ - private class PartialAccessPath extends TPartialAccessPath { + private class AccessPath extends TAccessPath { + /** Gets the head of this access path, if any. */ + abstract TypedContent getHead(); + + /** Gets the tail of this access path, if any. */ + abstract AccessPath getTail(); + + /** Gets the front of this access path. */ + abstract AccessPathFront getFront(); + + /** Gets the approximation of this access path. */ + abstract AccessPathApprox getApprox(); + + /** Gets the length of this access path. */ + abstract int length(); + + /** Gets a textual representation of this access path. */ abstract string toString(); - TypedContent getHead() { this = TPartialCons(result, _) } - - int len() { - this = TPartialNil(_) and result = 0 - or - this = TPartialCons(_, result) + /** Gets the access path obtained by popping `tc` from this access path, if any. */ + final AccessPath pop(TypedContent tc) { + result = this.getTail() and + tc = this.getHead() } - DataFlowType getType() { - this = TPartialNil(result) - or - exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) - } + /** Gets the access path obtained by pushing `tc` onto this access path. */ + final AccessPath push(TypedContent tc) { this = result.pop(tc) } } - private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { - override string toString() { - exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) - } + private class AccessPathNil extends AccessPath, TAccessPathNil { + private DataFlowType t; + + AccessPathNil() { this = TAccessPathNil(t) } + + DataFlowType getType() { result = t } + + override TypedContent getHead() { none() } + + override AccessPath getTail() { none() } + + override AccessPathFrontNil getFront() { result = TFrontNil(t) } + + override AccessPathApproxNil getApprox() { result = TNil(t) } + + override int length() { result = 0 } + + override string toString() { result = concat(": " + ppReprType(t)) } } - private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { - override string toString() { - exists(TypedContent tc, int len | this = TPartialCons(tc, len) | - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" + private class AccessPathCons extends AccessPath, TAccessPathCons { + private TypedContent head; + private AccessPath tail; + + AccessPathCons() { this = TAccessPathCons(head, tail) } + + override TypedContent getHead() { result = head } + + override AccessPath getTail() { result = tail } + + override AccessPathFrontHead getFront() { result = TFrontHead(head) } + + pragma[assume_small_delta] + override AccessPathApproxCons getApprox() { + result = TConsNil(head, tail.(AccessPathNil).getType()) + or + result = TConsCons(head, tail.getHead(), this.length()) + or + result = TCons1(head, this.length()) + } + + pragma[assume_small_delta] + override int length() { result = 1 + tail.length() } + + private string toStringImpl(boolean needsSuffix) { + exists(DataFlowType t | + tail = TAccessPathNil(t) and + needsSuffix = false and + result = head.toString() + "]" + concat(" : " + ppReprType(t)) + ) + or + result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) + or + exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | + result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true + or + result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false + ) + or + exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | + result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true + or + result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false ) } - } - private newtype TRevPartialAccessPath = - TRevPartialNil() or - TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `Content`s, but only the first - * element of the list and its length are tracked. - */ - private class RevPartialAccessPath extends TRevPartialAccessPath { - abstract string toString(); - - Content getHead() { this = TRevPartialCons(result, _) } - - int len() { - this = TRevPartialNil() and result = 0 - or - this = TRevPartialCons(_, result) - } - } - - private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { - override string toString() { result = "" } - } - - private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { override string toString() { - exists(Content c, int len | this = TRevPartialCons(c, len) | - if len = 1 - then result = "[" + c.toString() + "]" - else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" - ) + result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" + or + result = "[" + this.toStringImpl(false) } } - private predicate relevantState(FlowState state) { - sourceNode(_, state, _) or - sinkNode(_, state, _) or - additionalLocalStateStep(_, state, _, _, _) or - additionalLocalStateStep(_, _, _, state, _) or - additionalJumpStateStep(_, state, _, _, _) or - additionalJumpStateStep(_, _, _, state, _) + private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { + private TypedContent head1; + private TypedContent head2; + private int len; + + AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } + + override TypedContent getHead() { result = head1 } + + override AccessPath getTail() { + Stage5::consCand(head1, result.getApprox()) and + result.getHead() = head2 and + result.length() = len - 1 + } + + override AccessPathFrontHead getFront() { result = TFrontHead(head1) } + + override AccessPathApproxCons getApprox() { + result = TConsCons(head1, head2, len) or + result = TCons1(head1, len) + } + + override int length() { result = len } + + override string toString() { + if len = 2 + then result = "[" + head1.toString() + ", " + head2.toString() + "]" + else + result = + "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" + } } - private newtype TSummaryCtx1 = - TSummaryCtx1None() or - TSummaryCtx1Param(ParamNodeEx p) + private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { + private TypedContent head; + private int len; - private newtype TSummaryCtx2 = - TSummaryCtx2None() or - TSummaryCtx2Some(FlowState s) { relevantState(s) } + AccessPathCons1() { this = TAccessPathCons1(head, len) } - private newtype TSummaryCtx3 = - TSummaryCtx3None() or - TSummaryCtx3Some(PartialAccessPath ap) + override TypedContent getHead() { result = head } - private newtype TRevSummaryCtx1 = - TRevSummaryCtx1None() or - TRevSummaryCtx1Some(ReturnPosition pos) + override AccessPath getTail() { + Stage5::consCand(head, result.getApprox()) and result.length() = len - 1 + } - private newtype TRevSummaryCtx2 = - TRevSummaryCtx2None() or - TRevSummaryCtx2Some(FlowState s) { relevantState(s) } + override AccessPathFrontHead getFront() { result = TFrontHead(head) } - private newtype TRevSummaryCtx3 = - TRevSummaryCtx3None() or - TRevSummaryCtx3Some(RevPartialAccessPath ap) + override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - private newtype TPartialPathNode = - TPartialPathNodeFwd( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = TPartialNil(node.getDataFlowType()) and - exists(config.explorationLimit()) - or - partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap, config) and - distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() - } or - TPartialPathNodeRev( - NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, - RevPartialAccessPath ap, Configuration config - ) { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() and - exists(config.explorationLimit()) - or - revPartialPathStep(_, node, state, sc1, sc2, sc3, ap, config) and - not clearsContentEx(node, ap.getHead()) and + override int length() { result = len } + + override string toString() { + if len = 1 + then result = "[" + head.toString() + "]" + else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" + } + } + + abstract private class PathNodeImpl extends TPathNode { + /** Gets the `FlowState` of this node. */ + abstract FlowState getState(); + + /** Holds if this node is a source. */ + abstract predicate isSource(); + + abstract PathNodeImpl getASuccessorImpl(); + + private PathNodeImpl getASuccessorIfHidden() { + this.isHidden() and + result = this.getASuccessorImpl() + } + + pragma[nomagic] + private PathNodeImpl getANonHiddenSuccessor0() { + result = this.getASuccessorIfHidden*() and + not result.isHidden() + } + + final PathNodeImpl getANonHiddenSuccessor() { + result = this.getASuccessorImpl().getANonHiddenSuccessor0() and + not this.isHidden() + } + + abstract NodeEx getNodeEx(); + + predicate isHidden() { + not Config::includeHiddenNodes() and ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead()) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() + hiddenNode(this.getNodeEx().asNode()) and + not this.isSource() and + not this instanceof PathNodeSink + or + this.getNodeEx() instanceof TNodeImplicitRead + ) } - pragma[nomagic] - private predicate partialPathNodeMk0( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap, config) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - not clearsContentEx(node, ap.getHead().getContent()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead().getContent()) - ) and - if node.asNode() instanceof CastingNode - then compatibleTypes(node.getDataFlowType(), ap.getType()) - else any() - } + string getSourceGroup() { + this.isSource() and + Config::sourceGrouping(this.getNodeEx().asNode(), result) + } + + predicate isFlowSource() { + this.isSource() and not exists(this.getSourceGroup()) + or + this instanceof PathNodeSourceGroup + } + + predicate isFlowSink() { + this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or + this instanceof PathNodeSinkGroup + } + + private string ppAp() { + this instanceof PathNodeSink and result = "" + or + exists(string s | s = this.(PathNodeMid).getAp().toString() | + if s = "" then result = "" else result = " " + s + ) + } + + private string ppCtx() { + this instanceof PathNodeSink and result = "" + or + result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + } - /** - * A `Node` augmented with a call context, an access path, and a configuration. - */ - class PartialPathNode extends TPartialPathNode { /** Gets a textual representation of this element. */ string toString() { result = this.getNodeEx().toString() + this.ppAp() } @@ -4341,305 +3073,339 @@ private module FlowExploration { ) { this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) } + } + + /** Holds if `n` can reach a sink. */ + private predicate directReach(PathNodeImpl n) { + n instanceof PathNodeSink or + n instanceof PathNodeSinkGroup or + directReach(n.getANonHiddenSuccessor()) + } + + /** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ + private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } + + /** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ + private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { + n1.getANonHiddenSuccessor() = n2 and directReach(n2) + } + + private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) + + /** + * A `Node` augmented with a call context (except for sinks) and an access path. + * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. + */ + class PathNode instanceof PathNodeImpl { + PathNode() { reach(this) } + + /** Gets a textual representation of this element. */ + final string toString() { result = super.toString() } + + /** + * Gets a textual representation of this element, including a textual + * representation of the call context. + */ + final string toStringWithContext() { result = super.toStringWithContext() } + + /** + * 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/). + */ + final predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } /** Gets the underlying `Node`. */ - final Node getNode() { this.getNodeEx().projectToNode() = result } + final Node getNode() { super.getNodeEx().projectToNode() = result } - FlowState getState() { none() } - - private NodeEx getNodeEx() { - result = this.(PartialPathNodeFwd).getNodeEx() or - result = this.(PartialPathNodeRev).getNodeEx() - } - - /** Gets the associated configuration. */ - Configuration getConfiguration() { none() } + /** Gets the `FlowState` of this node. */ + final FlowState getState() { result = super.getState() } /** Gets a successor of this node, if any. */ - PartialPathNode getASuccessor() { none() } + final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } - /** - * Gets the approximate distance to the nearest source measured in number - * of interprocedural steps. - */ - int getSourceDistance() { - result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } + /** Holds if this node is a source. */ + final predicate isSource() { super.isSource() } - /** - * Gets the approximate distance to the nearest sink measured in number - * of interprocedural steps. - */ - int getSinkDistance() { - result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } + /** Holds if this node is a grouping of source nodes. */ + final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group) } - private string ppAp() { - exists(string s | - s = this.(PartialPathNodeFwd).getAp().toString() or - s = this.(PartialPathNodeRev).getAp().toString() - | - if s = "" then result = "" else result = " " + s - ) - } - - private string ppCtx() { - result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" - } - - /** Holds if this is a source in a forward-flow path. */ - predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } - - /** Holds if this is a sink in a reverse-flow path. */ - predicate isRevSink() { this.(PartialPathNodeRev).isSink() } + /** Holds if this node is a grouping of sink nodes. */ + final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group) } } /** * Provides the query predicates needed to include a graph in a path-problem query. */ - module PartialPathGraph { + module PathGraph { /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } + query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } + + /** Holds if `n` is a node in the graph of data flow path explanations. */ + query predicate nodes(PathNode n, string key, string val) { + key = "semmle.label" and val = n.toString() + } + + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through + * a subpath between `par` and `ret` with the connecting edges `arg -> par` and + * `ret -> out` is summarized as the edge `arg -> out`. + */ + query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { + Subpaths::subpaths(arg, par, ret, out) + } } - private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { + /** + * An intermediate flow graph node. This is a tuple consisting of a `Node`, + * a `FlowState`, a `CallContext`, a `SummaryCtx`, and an `AccessPath`. + */ + private class PathNodeMid extends PathNodeImpl, TPathNodeMid { NodeEx node; FlowState state; CallContext cc; - TSummaryCtx1 sc1; - TSummaryCtx2 sc2; - TSummaryCtx3 sc3; - PartialAccessPath ap; - Configuration config; + SummaryCtx sc; + AccessPath ap; - PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap, config) } + PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap) } - NodeEx getNodeEx() { result = node } + override NodeEx getNodeEx() { result = node } override FlowState getState() { result = state } CallContext getCallContext() { result = cc } - TSummaryCtx1 getSummaryCtx1() { result = sc1 } + SummaryCtx getSummaryCtx() { result = sc } - TSummaryCtx2 getSummaryCtx2() { result = sc2 } + AccessPath getAp() { result = ap } - TSummaryCtx3 getSummaryCtx3() { result = sc3 } - - PartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeFwd getASuccessor() { - partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp(), - result.getConfiguration()) + private PathNodeMid getSuccMid() { + pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), + result.getSummaryCtx(), result.getAp()) } - predicate isSource() { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap instanceof TPartialNil + override PathNodeImpl getASuccessorImpl() { + // an intermediate step to another intermediate node + result = this.getSuccMid() + or + // a final step to a sink + result = this.getSuccMid().projectToSink() + } + + override predicate isSource() { + sourceNode(node, state) and + ( + if hasSourceCallCtx() + then cc instanceof CallContextSomeCall + else cc instanceof CallContextAny + ) and + sc instanceof SummaryCtxNone and + ap = TAccessPathNil(node.getDataFlowType()) + } + + predicate isAtSink() { + sinkNode(node, state) and + ap instanceof AccessPathNil and + if hasSinkCallCtx() + then + // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` + // is exactly what we need to check. This also implies + // `sc instanceof SummaryCtxNone`. + // For `FeatureEqualSourceSinkCallContext` the initial call context was + // set to `CallContextSomeCall` and jumps are disallowed, so + // `cc instanceof CallContextNoCall` never holds. On the other hand, + // in this case there's never any need to enter a call except to identify + // a summary, so the condition in `pathIntoCallable` enforces this, which + // means that `sc instanceof SummaryCtxNone` holds if and only if we are + // in the call context of the source. + sc instanceof SummaryCtxNone or + cc instanceof CallContextNoCall + else any() + } + + PathNodeSink projectToSink() { + this.isAtSink() and + result.getNodeEx() = node and + result.getState() = state } } - private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { + /** + * A flow graph node corresponding to a sink. This is disjoint from the + * intermediate nodes in order to uniquely correspond to a given sink by + * excluding the `CallContext`. + */ + private class PathNodeSink extends PathNodeImpl, TPathNodeSink { NodeEx node; FlowState state; - TRevSummaryCtx1 sc1; - TRevSummaryCtx2 sc2; - TRevSummaryCtx3 sc3; - RevPartialAccessPath ap; - Configuration config; - PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap, config) } + PathNodeSink() { this = TPathNodeSink(node, state) } - NodeEx getNodeEx() { result = node } + override NodeEx getNodeEx() { result = node } override FlowState getState() { result = state } - TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } + override PathNodeImpl getASuccessorImpl() { result = TPathNodeSinkGroup(this.getSinkGroup()) } - TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } + override predicate isSource() { sourceNode(node, state) } - TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } + string getSinkGroup() { Config::sinkGrouping(node.asNode(), result) } + } - RevPartialAccessPath getAp() { result = ap } + private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { + string sourceGroup; - override Configuration getConfiguration() { result = config } + PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup) } - override PartialPathNodeRev getASuccessor() { - revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), - this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp(), this.getConfiguration()) - } + override NodeEx getNodeEx() { none() } - predicate isSink() { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() + override FlowState getState() { none() } + + override PathNodeImpl getASuccessorImpl() { result.getSourceGroup() = sourceGroup } + + override predicate isSource() { none() } + + override string toString() { result = sourceGroup } + + override 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 } } - private predicate partialPathStep( - PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, - TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config + private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { + string sinkGroup; + + PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup) } + + override NodeEx getNodeEx() { none() } + + override FlowState getState() { none() } + + override PathNodeImpl getASuccessorImpl() { none() } + + override predicate isSource() { none() } + + override string toString() { result = sinkGroup } + + override 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 + } + } + + private predicate pathNode( + PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, + LocalCallContext localCC ) { - not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and - ( - localFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - ) - or - jumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - partialPathStoreStep(mid, _, _, node, ap) and + midnode = mid.getNodeEx() and state = mid.getState() and cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(PartialAccessPath ap0, TypedContent tc | - partialPathReadStep(mid, ap0, tc, node, cc, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsFwd(ap, tc, ap0, config) - ) - or - partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap, config) - or - partialPathOutOfCallable(mid, node, state, cc, ap, config) and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() - or - partialPathThroughCallable(mid, node, state, cc, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() + sc = mid.getSummaryCtx() and + localCC = + getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), + midnode.getEnclosingCallable()) and + ap = mid.getAp() } - bindingset[result, i] - private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } - - pragma[inline] - private predicate partialPathStoreStep( - PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, - PartialAccessPath ap2 + /** + * Holds if data may flow from `mid` to `node`. The last step in or out of + * a callable is recorded by `cc`. + */ + pragma[assume_small_delta] + pragma[nomagic] + private predicate pathStep( + PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap ) { - exists(NodeEx midNode, DataFlowType contentType | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - store(midNode, tc, node, contentType, mid.getConfiguration()) and - ap2.getHead() = tc and - ap2.len() = unbindInt(ap1.len() + 1) and - compatibleTypes(ap1.getType(), contentType) + exists(NodeEx midnode, FlowState state0, LocalCallContext localCC | + pathNode(mid, midnode, state0, cc, sc, ap, localCC) and + localFlowBigStep(midnode, state0, node, state, true, _, localCC) ) + or + exists(AccessPath ap0, NodeEx midnode, FlowState state0, LocalCallContext localCC | + pathNode(mid, midnode, state0, cc, sc, ap0, localCC) and + localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), localCC) and + ap0 instanceof AccessPathNil + ) + or + jumpStepEx(mid.getNodeEx(), node) and + state = mid.getState() and + cc instanceof CallContextAny and + sc instanceof SummaryCtxNone and + ap = mid.getAp() + or + additionalJumpStep(mid.getNodeEx(), node) and + state = mid.getState() and + cc instanceof CallContextAny and + sc instanceof SummaryCtxNone and + mid.getAp() instanceof AccessPathNil and + ap = TAccessPathNil(node.getDataFlowType()) + or + additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state) and + cc instanceof CallContextAny and + sc instanceof SummaryCtxNone and + mid.getAp() instanceof AccessPathNil and + ap = TAccessPathNil(node.getDataFlowType()) + or + exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and + sc = mid.getSummaryCtx() + or + exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and + sc = mid.getSummaryCtx() + or + pathIntoCallable(mid, node, state, _, cc, sc, _) and ap = mid.getAp() + or + pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone + or + pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() } pragma[nomagic] - private predicate apConsFwd( - PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config + private predicate pathReadStep( + PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc ) { - exists(PartialPathNodeFwd mid | - partialPathStoreStep(mid, ap1, tc, _, ap2) and - config = mid.getConfiguration() - ) + ap0 = mid.getAp() and + tc = ap0.getHead() and + Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node) and + state = mid.getState() and + cc = mid.getCallContext() } pragma[nomagic] - private predicate partialPathReadStep( - PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc, - Configuration config + private predicate pathStoreStep( + PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and - ap.getHead() = tc and - pragma[only_bind_into](config) = mid.getConfiguration() and - cc = mid.getCallContext() - ) + ap0 = mid.getAp() and + Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _) and + state = mid.getState() and + cc = mid.getCallContext() } - private predicate partialPathOutOfCallable0( - PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, - PartialAccessPath ap, Configuration config + private predicate pathOutOfCallable0( + PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa ) { pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and state = mid.getState() and innercc = mid.getCallContext() and innercc instanceof CallContextNoCall and - ap = mid.getAp() and - config = mid.getConfiguration() + apa = mid.getAp().getApprox() } pragma[nomagic] - private predicate partialPathOutOfCallable1( - PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config + private predicate pathOutOfCallable1( + PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, + AccessPathApprox apa ) { exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - partialPathOutOfCallable0(mid, pos, state, innercc, ap, config) and + pathOutOfCallable0(mid, pos, state, innercc, apa) and c = pos.getCallable() and kind = pos.getKind() and resolveReturn(innercc, c, call) @@ -4648,53 +3414,86 @@ private module FlowExploration { ) } - private predicate partialPathOutOfCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(ReturnKindExt kind, DataFlowCall call | - partialPathOutOfCallable1(mid, call, kind, state, cc, ap, config) - | - out.asNode() = kind.getAnOutNode(call) + pragma[noinline] + private NodeEx getAnOutNodeFlow(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa) { + result.asNode() = kind.getAnOutNode(call) and + Stage5::revFlow(result, _, apa) + } + + /** + * Holds if data may flow from `mid` to `out`. The last step of this path + * is a return from a callable and is recorded by `cc`, if needed. + */ + pragma[noinline] + private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { + exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa | + pathOutOfCallable1(mid, call, kind, state, cc, apa) and + out = getAnOutNodeFlow(kind, call, apa) ) } + /** + * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. + */ pragma[noinline] - private predicate partialPathIntoArg( - PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, - DataFlowCall call, PartialAccessPath ap, Configuration config + private predicate pathIntoArg( + PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, + AccessPath ap, AccessPathApprox apa ) { - exists(ArgNode arg, ArgumentPosition apos | - arg = mid.getNodeEx().asNode() and - state = mid.getState() and - cc = mid.getCallContext() and - arg.argumentOf(call, apos) and - ap = mid.getAp() and - config = mid.getConfiguration() and + exists(ArgNodeEx arg, ArgumentPosition apos | + pathNode(mid, arg, state, cc, _, ap, _) and + arg.asNode().(ArgNode).argumentOf(call, apos) and + apa = ap.getApprox() and parameterMatch(ppos, apos) ) } pragma[nomagic] - private predicate partialPathIntoCallable0( - PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, PartialAccessPath ap, Configuration config + private predicate parameterCand( + DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa ) { - partialPathIntoArg(mid, pos, state, outercc, call, ap, config) and - callable = resolveCall(call, outercc) + exists(ParamNodeEx p | + Stage5::revFlow(p, _, apa) and + p.isParameterOf(callable, pos) + ) } - private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, - CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, - DataFlowCall call, PartialAccessPath ap, Configuration config + pragma[nomagic] + private predicate pathIntoCallable0( + PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, + CallContext outercc, DataFlowCall call, AccessPath ap ) { - exists(ParameterPosition pos, DataFlowCallable callable | - partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and + exists(AccessPathApprox apa | + pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, + pragma[only_bind_into](apa)) and + callable = resolveCall(call, outercc) and + parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa)) + ) + } + + /** + * Holds if data may flow from `mid` to `p` through `call`. The contexts + * before and after entering the callable are `outercc` and `innercc`, + * respectively. + */ + pragma[nomagic] + private predicate pathIntoCallable( + PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, + SummaryCtx sc, DataFlowCall call + ) { + exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | + pathIntoCallable0(mid, callable, pos, state, outercc, call, ap) and p.isParameterOf(callable, pos) and - sc1 = TSummaryCtx1Param(p) and - sc2 = TSummaryCtx2Some(state) and - sc3 = TSummaryCtx3Some(ap) + ( + sc = TSummaryCtxSome(p, state, ap) + or + not exists(TSummaryCtxSome(p, state, ap)) and + sc = TSummaryCtxNone() and + // When the call contexts of source and sink needs to match then there's + // never any reason to enter a callable except to find a summary. See also + // the comment in `PathNodeMid::isAtSink`. + not Config::getAFeature() instanceof FeatureEqualSourceSinkCallContext + ) | if recordDataFlowCallSite(call, callable) then innercc = TSpecificCall(call) @@ -4702,252 +3501,1108 @@ private module FlowExploration { ) } + /** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ pragma[nomagic] - private predicate paramFlowsThroughInPartialPath( - ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config + private predicate paramFlowsThrough( + ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, + AccessPathApprox apa ) { - exists(PartialPathNodeFwd mid, RetNodeEx ret | - mid.getNodeEx() = ret and + exists(RetNodeEx ret | + pathNode(_, ret, state, cc, sc, ap, _) and kind = ret.getKind() and + apa = ap.getApprox() and + parameterFlowThroughAllowed(sc.getParamNode(), kind) + ) + } + + pragma[assume_small_delta] + pragma[nomagic] + private predicate pathThroughCallable0( + DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, + AccessPath ap, AccessPathApprox apa + ) { + exists(CallContext innercc, SummaryCtx sc | + pathIntoCallable(mid, _, _, cc, innercc, sc, call) and + paramFlowsThrough(kind, state, innercc, sc, ap, apa) + ) + } + + /** + * Holds if data may flow from `mid` through a callable to the node `out`. + * The context `cc` is restored to its value prior to entering the callable. + */ + pragma[noinline] + private predicate pathThroughCallable( + PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap + ) { + exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa | + pathThroughCallable0(call, mid, kind, state, cc, ap, apa) and + out = getAnOutNodeFlow(kind, call, apa) + ) + } + + private module Subpaths { + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by + * `kind`, `sc`, `apout`, and `innercc`. + */ + pragma[nomagic] + private predicate subpaths01( + PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, + NodeEx out, FlowState sout, AccessPath apout + ) { + pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and + pathIntoCallable(arg, par, _, _, innercc, sc, _) and + paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, + pragma[only_bind_into](apout), _) and + not arg.isHidden() + } + + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by + * `kind`, `sc`, `sout`, `apout`, and `innercc`. + */ + pragma[nomagic] + private predicate subpaths02( + PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, + NodeEx out, FlowState sout, AccessPath apout + ) { + subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and + out.asNode() = kind.getAnOutNode(_) + } + + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple. + */ + pragma[nomagic] + private predicate subpaths03( + PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, + AccessPath apout + ) { + exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | + subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and + pathNode(ret, retnode, sout, innercc, sc, apout, _) and + kind = retnode.getKind() + ) + } + + private PathNodeImpl localStepToHidden(PathNodeImpl n) { + n.getASuccessorImpl() = result and + result.isHidden() and + exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | + localFlowBigStep(n1, _, n2, _, _, _, _) or + storeEx(n1, _, n2, _) or + readSetEx(n1, _, n2) + ) + } + + pragma[nomagic] + private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { + succ = pred.getANonHiddenSuccessor() and + succNode = succ.getNodeEx() + } + + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through + * a subpath between `par` and `ret` with the connecting edges `arg -> par` and + * `ret -> out` is summarized as the edge `arg -> out`. + */ + predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { + exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | + pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and + subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and + hasSuccessor(pragma[only_bind_into](arg), par, p) and + not ret.isHidden() and + pathNode(out0, o, sout, _, _, apout, _) + | + out = out0 or out = out0.projectToSink() + ) + } + + /** + * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. + */ + predicate retReach(PathNodeImpl n) { + exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) + or + exists(PathNodeImpl mid | + retReach(mid) and + n.getANonHiddenSuccessor() = mid and + not subpaths(_, mid, _, _) + ) + } + } + + /** + * Holds if data can flow from `source` to `sink`. + * + * The corresponding paths are generated from the end-points and the graph + * included in the module `PathGraph`. + */ + predicate hasFlowPath(PathNode source, PathNode sink) { + exists(PathNodeImpl flowsource, PathNodeImpl flowsink | + source = flowsource and sink = flowsink + | + flowsource.isFlowSource() and + (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and + flowsink.isFlowSink() + ) + } + + private predicate flowsTo(PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink) { + flowsource.isSource() and + flowsource.getNodeEx().asNode() = source and + (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and + flowsink.getNodeEx().asNode() = sink + } + + /** + * Holds if data can flow from `source` to `sink`. + */ + predicate hasFlow(Node source, Node sink) { flowsTo(_, _, source, sink) } + + /** + * Holds if data can flow from some source to `sink`. + */ + predicate hasFlowTo(Node sink) { sink = any(PathNodeSink n).getNodeEx().asNode() } + + /** + * Holds if data can flow from some source to `sink`. + */ + predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) } + + private predicate finalStats( + boolean fwd, int nodes, int fields, int conscand, int states, int tuples + ) { + fwd = true and + nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and + fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and + conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and + states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and + tuples = count(PathNodeImpl pn) + or + fwd = false and + nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and + fields = + count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and + conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and + states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and + tuples = count(PathNode pn) + } + + /** + * INTERNAL: Only for debugging. + * + * Calculates per-stage metrics for data flow. + */ + predicate stageStats( + int n, string stage, int nodes, int fields, int conscand, int states, int tuples + ) { + stage = "1 Fwd" and + n = 10 and + Stage1::stats(true, nodes, fields, conscand, states, tuples) + or + stage = "1 Rev" and + n = 15 and + Stage1::stats(false, nodes, fields, conscand, states, tuples) + or + stage = "2 Fwd" and + n = 20 and + Stage2::stats(true, nodes, fields, conscand, states, tuples) + or + stage = "2 Rev" and + n = 25 and + Stage2::stats(false, nodes, fields, conscand, states, tuples) + or + stage = "3 Fwd" and + n = 30 and + Stage3::stats(true, nodes, fields, conscand, states, tuples) + or + stage = "3 Rev" and + n = 35 and + Stage3::stats(false, nodes, fields, conscand, states, tuples) + or + stage = "4 Fwd" and + n = 40 and + Stage4::stats(true, nodes, fields, conscand, states, tuples) + or + stage = "4 Rev" and + n = 45 and + Stage4::stats(false, nodes, fields, conscand, states, tuples) + or + stage = "5 Fwd" and + n = 50 and + Stage5::stats(true, nodes, fields, conscand, states, tuples) + or + stage = "5 Rev" and + n = 55 and + Stage5::stats(false, nodes, fields, conscand, states, tuples) + or + stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) + or + stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) + } + + module FlowExploration { + private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2) { + exists(NodeEx node1, NodeEx node2 | + jumpStepEx(node1, node2) + or + additionalJumpStep(node1, node2) + or + additionalJumpStateStep(node1, _, node2, _) + or + // flow into callable + viableParamArgEx(_, node2, node1) + or + // flow out of a callable + viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) + | + c1 = node1.getEnclosingCallable() and + c2 = node2.getEnclosingCallable() and + c1 != c2 + ) + } + + private predicate interestingCallableSrc(DataFlowCallable c) { + exists(Node n | Config::isSource(n, _) and c = getNodeEnclosingCallable(n)) + or + exists(DataFlowCallable mid | interestingCallableSrc(mid) and callableStep(mid, c)) + } + + private predicate interestingCallableSink(DataFlowCallable c) { + exists(Node n | Config::isSink(n, _) and c = getNodeEnclosingCallable(n)) + or + exists(DataFlowCallable mid | interestingCallableSink(mid) and callableStep(c, mid)) + } + + private newtype TCallableExt = + TCallable(DataFlowCallable c) { + interestingCallableSrc(c) or + interestingCallableSink(c) + } or + TCallableSrc() or + TCallableSink() + + private predicate callableExtSrc(TCallableSrc src) { any() } + + private predicate callableExtSink(TCallableSink sink) { any() } + + private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { + exists(DataFlowCallable c1, DataFlowCallable c2 | + callableStep(c1, c2) and + ce1 = TCallable(c1) and + ce2 = TCallable(c2) + ) + or + exists(Node n | + ce1 = TCallableSrc() and + Config::isSource(n, _) and + ce2 = TCallable(getNodeEnclosingCallable(n)) + ) + or + exists(Node n | + ce2 = TCallableSink() and + Config::isSink(n, _) and + ce1 = TCallable(getNodeEnclosingCallable(n)) + ) + } + + private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { + callableExtStepFwd(ce2, ce1) + } + + private int distSrcExt(TCallableExt c) = + shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) + + private int distSinkExt(TCallableExt c) = + shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) + + private int distSrc(DataFlowCallable c) { result = distSrcExt(TCallable(c)) - 1 } + + private int distSink(DataFlowCallable c) { result = distSinkExt(TCallable(c)) - 1 } + + private newtype TPartialAccessPath = + TPartialNil(DataFlowType t) or + TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } + + /** + * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first + * element of the list and its length are tracked. If data flows from a source to + * a given node with a given `AccessPath`, this indicates the sequence of + * dereference operations needed to get from the value in the node to the + * tracked object. The final type indicates the type of the tracked object. + */ + private class PartialAccessPath extends TPartialAccessPath { + abstract string toString(); + + TypedContent getHead() { this = TPartialCons(result, _) } + + int len() { + this = TPartialNil(_) and result = 0 + or + this = TPartialCons(_, result) + } + + DataFlowType getType() { + this = TPartialNil(result) + or + exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) + } + } + + private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { + override string toString() { + exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) + } + } + + private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { + override string toString() { + exists(TypedContent tc, int len | this = TPartialCons(tc, len) | + if len = 1 + then result = "[" + tc.toString() + "]" + else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" + ) + } + } + + private newtype TRevPartialAccessPath = + TRevPartialNil() or + TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } + + /** + * Conceptually a list of `Content`s, but only the first + * element of the list and its length are tracked. + */ + private class RevPartialAccessPath extends TRevPartialAccessPath { + abstract string toString(); + + Content getHead() { this = TRevPartialCons(result, _) } + + int len() { + this = TRevPartialNil() and result = 0 + or + this = TRevPartialCons(_, result) + } + } + + private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { + override string toString() { result = "" } + } + + private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { + override string toString() { + exists(Content c, int len | this = TRevPartialCons(c, len) | + if len = 1 + then result = "[" + c.toString() + "]" + else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" + ) + } + } + + private predicate relevantState(FlowState state) { + sourceNode(_, state) or + sinkNode(_, state) or + additionalLocalStateStep(_, state, _, _) or + additionalLocalStateStep(_, _, _, state) or + additionalJumpStateStep(_, state, _, _) or + additionalJumpStateStep(_, _, _, state) + } + + private newtype TSummaryCtx1 = + TSummaryCtx1None() or + TSummaryCtx1Param(ParamNodeEx p) + + private newtype TSummaryCtx2 = + TSummaryCtx2None() or + TSummaryCtx2Some(FlowState s) { relevantState(s) } + + private newtype TSummaryCtx3 = + TSummaryCtx3None() or + TSummaryCtx3Some(PartialAccessPath ap) + + private newtype TRevSummaryCtx1 = + TRevSummaryCtx1None() or + TRevSummaryCtx1Some(ReturnPosition pos) + + private newtype TRevSummaryCtx2 = + TRevSummaryCtx2None() or + TRevSummaryCtx2Some(FlowState s) { relevantState(s) } + + private newtype TRevSummaryCtx3 = + TRevSummaryCtx3None() or + TRevSummaryCtx3Some(RevPartialAccessPath ap) + + private newtype TPartialPathNode = + TPartialPathNodeFwd( + NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, + TSummaryCtx3 sc3, PartialAccessPath ap + ) { + sourceNode(node, state) and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() and + ap = TPartialNil(node.getDataFlowType()) and + exists(explorationLimit()) + or + partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap) and + distSrc(node.getEnclosingCallable()) <= explorationLimit() + } or + TPartialPathNodeRev( + NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, + RevPartialAccessPath ap + ) { + sinkNode(node, state) and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + sc3 = TRevSummaryCtx3None() and + ap = TRevPartialNil() and + exists(explorationLimit()) + or + revPartialPathStep(_, node, state, sc1, sc2, sc3, ap) and + not clearsContentEx(node, ap.getHead()) and + ( + notExpectsContent(node) or + expectsContentEx(node, ap.getHead()) + ) and + not fullBarrier(node) and + not stateBarrier(node, state) and + distSink(node.getEnclosingCallable()) <= explorationLimit() + } + + pragma[nomagic] + private predicate partialPathNodeMk0( + NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, + TSummaryCtx3 sc3, PartialAccessPath ap + ) { + partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap) and + not fullBarrier(node) and + not stateBarrier(node, state) and + not clearsContentEx(node, ap.getHead().getContent()) and + ( + notExpectsContent(node) or + expectsContentEx(node, ap.getHead().getContent()) + ) and + if node.asNode() instanceof CastingNode + then compatibleTypes(node.getDataFlowType(), ap.getType()) + else any() + } + + /** + * A `Node` augmented with a call context, an access path, and a configuration. + */ + class PartialPathNode extends TPartialPathNode { + /** Gets a textual representation of this element. */ + string toString() { result = this.getNodeEx().toString() + this.ppAp() } + + /** + * Gets a textual representation of this element, including a textual + * representation of the call context. + */ + string toStringWithContext() { + result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() + } + + /** + * 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 + ) { + this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + + /** Gets the underlying `Node`. */ + final Node getNode() { this.getNodeEx().projectToNode() = result } + + FlowState getState() { none() } + + private NodeEx getNodeEx() { + result = this.(PartialPathNodeFwd).getNodeEx() or + result = this.(PartialPathNodeRev).getNodeEx() + } + + /** Gets a successor of this node, if any. */ + PartialPathNode getASuccessor() { none() } + + /** + * Gets the approximate distance to the nearest source measured in number + * of interprocedural steps. + */ + int getSourceDistance() { result = distSrc(this.getNodeEx().getEnclosingCallable()) } + + /** + * Gets the approximate distance to the nearest sink measured in number + * of interprocedural steps. + */ + int getSinkDistance() { result = distSink(this.getNodeEx().getEnclosingCallable()) } + + private string ppAp() { + exists(string s | + s = this.(PartialPathNodeFwd).getAp().toString() or + s = this.(PartialPathNodeRev).getAp().toString() + | + if s = "" then result = "" else result = " " + s + ) + } + + private string ppCtx() { + result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" + } + + /** Holds if this is a source in a forward-flow path. */ + predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } + + /** Holds if this is a sink in a reverse-flow path. */ + predicate isRevSink() { this.(PartialPathNodeRev).isSink() } + } + + /** + * Provides the query predicates needed to include a graph in a path-problem query. + */ + module PartialPathGraph { + /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ + query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } + } + + private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { + NodeEx node; + FlowState state; + CallContext cc; + TSummaryCtx1 sc1; + TSummaryCtx2 sc2; + TSummaryCtx3 sc3; + PartialAccessPath ap; + + PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap) } + + NodeEx getNodeEx() { result = node } + + override FlowState getState() { result = state } + + CallContext getCallContext() { result = cc } + + TSummaryCtx1 getSummaryCtx1() { result = sc1 } + + TSummaryCtx2 getSummaryCtx2() { result = sc2 } + + TSummaryCtx3 getSummaryCtx3() { result = sc3 } + + PartialAccessPath getAp() { result = ap } + + override PartialPathNodeFwd getASuccessor() { + partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), + result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp()) + } + + predicate isSource() { + sourceNode(node, state) and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() and + ap instanceof TPartialNil + } + } + + private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { + NodeEx node; + FlowState state; + TRevSummaryCtx1 sc1; + TRevSummaryCtx2 sc2; + TRevSummaryCtx3 sc3; + RevPartialAccessPath ap; + + PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap) } + + NodeEx getNodeEx() { result = node } + + override FlowState getState() { result = state } + + TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } + + TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } + + TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } + + RevPartialAccessPath getAp() { result = ap } + + override PartialPathNodeRev getASuccessor() { + revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), + this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp()) + } + + predicate isSink() { + sinkNode(node, state) and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + sc3 = TRevSummaryCtx3None() and + ap = TRevPartialNil() + } + } + + private predicate partialPathStep( + PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, + TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap + ) { + not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and + ( + localFlowStepEx(mid.getNodeEx(), node) and + state = mid.getState() and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + ap = mid.getAp() + or + additionalLocalFlowStep(mid.getNodeEx(), node) and + state = mid.getState() and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + mid.getAp() instanceof PartialAccessPathNil and + ap = TPartialNil(node.getDataFlowType()) + or + additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state) and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + mid.getAp() instanceof PartialAccessPathNil and + ap = TPartialNil(node.getDataFlowType()) + ) + or + jumpStepEx(mid.getNodeEx(), node) and + state = mid.getState() and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() and + ap = mid.getAp() + or + additionalJumpStep(mid.getNodeEx(), node) and + state = mid.getState() and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() and + mid.getAp() instanceof PartialAccessPathNil and + ap = TPartialNil(node.getDataFlowType()) + or + additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state) and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() and + mid.getAp() instanceof PartialAccessPathNil and + ap = TPartialNil(node.getDataFlowType()) + or + partialPathStoreStep(mid, _, _, node, ap) and state = mid.getState() and cc = mid.getCallContext() and sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() and + sc3 = mid.getSummaryCtx3() + or + exists(PartialAccessPath ap0, TypedContent tc | + partialPathReadStep(mid, ap0, tc, node, cc) and + state = mid.getState() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + apConsFwd(ap, tc, ap0) + ) + or + partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap) + or + partialPathOutOfCallable(mid, node, state, cc, ap) and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + sc3 = TSummaryCtx3None() + or + partialPathThroughCallable(mid, node, state, cc, ap) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() + } + + bindingset[result, i] + private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } + + pragma[inline] + private predicate partialPathStoreStep( + PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, + PartialAccessPath ap2 + ) { + exists(NodeEx midNode, DataFlowType contentType | + midNode = mid.getNodeEx() and + ap1 = mid.getAp() and + storeEx(midNode, tc, node, contentType) and + ap2.getHead() = tc and + ap2.len() = unbindInt(ap1.len() + 1) and + compatibleTypes(ap1.getType(), contentType) + ) + } + + pragma[nomagic] + private predicate apConsFwd(PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2) { + partialPathStoreStep(_, ap1, tc, _, ap2) + } + + pragma[nomagic] + private predicate partialPathReadStep( + PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc + ) { + exists(NodeEx midNode | + midNode = mid.getNodeEx() and + ap = mid.getAp() and + read(midNode, tc.getContent(), node) and + ap.getHead() = tc and + cc = mid.getCallContext() + ) + } + + private predicate partialPathOutOfCallable0( + PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, + PartialAccessPath ap + ) { + pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and + state = mid.getState() and + innercc = mid.getCallContext() and + innercc instanceof CallContextNoCall and ap = mid.getAp() - ) - } + } - pragma[noinline] - private predicate partialPathThroughCallable0( - DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | - partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _, config) and - paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap, config) - ) - } + pragma[nomagic] + private predicate partialPathOutOfCallable1( + PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, + CallContext cc, PartialAccessPath ap + ) { + exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | + partialPathOutOfCallable0(mid, pos, state, innercc, ap) and + c = pos.getCallable() and + kind = pos.getKind() and + resolveReturn(innercc, c, call) + | + if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + ) + } - private predicate partialPathThroughCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ReturnKindExt kind | - partialPathThroughCallable0(call, mid, kind, state, cc, ap, config) and - out.asNode() = kind.getAnOutNode(call) - ) - } + private predicate partialPathOutOfCallable( + PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap + ) { + exists(ReturnKindExt kind, DataFlowCall call | + partialPathOutOfCallable1(mid, call, kind, state, cc, ap) + | + out.asNode() = kind.getAnOutNode(call) + ) + } - pragma[nomagic] - private predicate revPartialPathStep( - PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, - TRevSummaryCtx3 sc3, RevPartialAccessPath ap, Configuration config - ) { - localFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - jumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - revPartialPathReadStep(mid, _, _, node, ap) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(RevPartialAccessPath ap0, Content c | - revPartialPathStoreStep(mid, ap0, c, node, config) and + pragma[noinline] + private predicate partialPathIntoArg( + PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, + DataFlowCall call, PartialAccessPath ap + ) { + exists(ArgNode arg, ArgumentPosition apos | + arg = mid.getNodeEx().asNode() and + state = mid.getState() and + cc = mid.getCallContext() and + arg.argumentOf(call, apos) and + ap = mid.getAp() and + parameterMatch(ppos, apos) + ) + } + + pragma[nomagic] + private predicate partialPathIntoCallable0( + PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, + CallContext outercc, DataFlowCall call, PartialAccessPath ap + ) { + partialPathIntoArg(mid, pos, state, outercc, call, ap) and + callable = resolveCall(call, outercc) + } + + private predicate partialPathIntoCallable( + PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, + CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, + DataFlowCall call, PartialAccessPath ap + ) { + exists(ParameterPosition pos, DataFlowCallable callable | + partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap) and + p.isParameterOf(callable, pos) and + sc1 = TSummaryCtx1Param(p) and + sc2 = TSummaryCtx2Some(state) and + sc3 = TSummaryCtx3Some(ap) + | + if recordDataFlowCallSite(call, callable) + then innercc = TSpecificCall(call) + else innercc = TSomeCall() + ) + } + + pragma[nomagic] + private predicate paramFlowsThroughInPartialPath( + ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, + TSummaryCtx3 sc3, PartialAccessPath ap + ) { + exists(PartialPathNodeFwd mid, RetNodeEx ret | + mid.getNodeEx() = ret and + kind = ret.getKind() and + state = mid.getState() and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + ap = mid.getAp() + ) + } + + pragma[noinline] + private predicate partialPathThroughCallable0( + DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, + CallContext cc, PartialAccessPath ap + ) { + exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | + partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _) and + paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap) + ) + } + + private predicate partialPathThroughCallable( + PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap + ) { + exists(DataFlowCall call, ReturnKindExt kind | + partialPathThroughCallable0(call, mid, kind, state, cc, ap) and + out.asNode() = kind.getAnOutNode(call) + ) + } + + pragma[nomagic] + private predicate revPartialPathStep( + PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, + TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, RevPartialAccessPath ap + ) { + localFlowStepEx(node, mid.getNodeEx()) and state = mid.getState() and sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and sc3 = mid.getSummaryCtx3() and - apConsRev(ap, c, ap0, config) - ) - or - exists(ParamNodeEx p | - mid.getNodeEx() = p and - viableParamArgEx(_, p, node) and + ap = mid.getAp() + or + additionalLocalFlowStep(node, mid.getNodeEx()) and state = mid.getState() and sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and sc3 = mid.getSummaryCtx3() and + mid.getAp() instanceof RevPartialAccessPathNil and + ap = TRevPartialNil() + or + additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState()) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + mid.getAp() instanceof RevPartialAccessPathNil and + ap = TRevPartialNil() + or + jumpStepEx(node, mid.getNodeEx()) and + state = mid.getState() and sc1 = TRevSummaryCtx1None() and sc2 = TRevSummaryCtx2None() and sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - or - exists(ReturnPosition pos | - revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap, config) and - pos = getReturnPosition(node.asNode()) - ) - or - revPartialPathThroughCallable(mid, node, state, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - pragma[inline] - private predicate revPartialPathReadStep( - PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, - RevPartialAccessPath ap2 - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - read(node, c, midNode, mid.getConfiguration()) and - ap2.getHead() = c and - ap2.len() = unbindInt(ap1.len() + 1) - ) - } - - pragma[nomagic] - private predicate apConsRev( - RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeRev mid | - revPartialPathReadStep(mid, ap1, c, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathStoreStep( - PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config - ) { - exists(NodeEx midNode, TypedContent tc | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - store(node, tc, midNode, _, config) and - ap.getHead() = c and - config = mid.getConfiguration() and - tc.getContent() = c - ) - } - - pragma[nomagic] - private predicate revPartialPathIntoReturn( - PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, - TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap, - Configuration config - ) { - exists(NodeEx out | - mid.getNodeEx() = out and - mid.getState() = state and - viableReturnPosOutEx(call, pos, out) and - sc1 = TRevSummaryCtx1Some(pos) and - sc2 = TRevSummaryCtx2Some(state) and - sc3 = TRevSummaryCtx3Some(ap) and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathFlowsThrough( - ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, - TRevSummaryCtx3Some sc3, RevPartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | - mid.getNodeEx() = p and - mid.getState() = state and - p.getPosition() = ppos and + ap = mid.getAp() + or + additionalJumpStep(node, mid.getNodeEx()) and + state = mid.getState() and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + sc3 = TRevSummaryCtx3None() and + mid.getAp() instanceof RevPartialAccessPathNil and + ap = TRevPartialNil() + or + additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState()) and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + sc3 = TRevSummaryCtx3None() and + mid.getAp() instanceof RevPartialAccessPathNil and + ap = TRevPartialNil() + or + revPartialPathReadStep(mid, _, _, node, ap) and + state = mid.getState() and sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } + sc3 = mid.getSummaryCtx3() + or + exists(RevPartialAccessPath ap0, Content c | + revPartialPathStoreStep(mid, ap0, c, node) and + state = mid.getState() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + apConsRev(ap, c, ap0) + ) + or + exists(ParamNodeEx p | + mid.getNodeEx() = p and + viableParamArgEx(_, p, node) and + state = mid.getState() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + sc3 = TRevSummaryCtx3None() and + ap = mid.getAp() + ) + or + exists(ReturnPosition pos | + revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap) and + pos = getReturnPosition(node.asNode()) + ) + or + revPartialPathThroughCallable(mid, node, state, ap) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() + } - pragma[nomagic] - private predicate revPartialPathThroughCallable0( - DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, - RevPartialAccessPath ap, Configuration config - ) { - exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | - revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _, config) and - revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap, config) - ) - } + pragma[inline] + private predicate revPartialPathReadStep( + PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, + RevPartialAccessPath ap2 + ) { + exists(NodeEx midNode | + midNode = mid.getNodeEx() and + ap1 = mid.getAp() and + read(node, c, midNode) and + ap2.getHead() = c and + ap2.len() = unbindInt(ap1.len() + 1) + ) + } - pragma[nomagic] - private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ArgumentPosition pos | - revPartialPathThroughCallable0(call, mid, pos, state, ap, config) and - node.asNode().(ArgNode).argumentOf(call, pos) - ) + pragma[nomagic] + private predicate apConsRev(RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2) { + revPartialPathReadStep(_, ap1, c, _, ap2) + } + + pragma[nomagic] + private predicate revPartialPathStoreStep( + PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node + ) { + exists(NodeEx midNode, TypedContent tc | + midNode = mid.getNodeEx() and + ap = mid.getAp() and + storeEx(node, tc, midNode, _) and + ap.getHead() = c and + tc.getContent() = c + ) + } + + pragma[nomagic] + private predicate revPartialPathIntoReturn( + PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, + TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap + ) { + exists(NodeEx out | + mid.getNodeEx() = out and + mid.getState() = state and + viableReturnPosOutEx(call, pos, out) and + sc1 = TRevSummaryCtx1Some(pos) and + sc2 = TRevSummaryCtx2Some(state) and + sc3 = TRevSummaryCtx3Some(ap) and + ap = mid.getAp() + ) + } + + pragma[nomagic] + private predicate revPartialPathFlowsThrough( + ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, + TRevSummaryCtx3Some sc3, RevPartialAccessPath ap + ) { + exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | + mid.getNodeEx() = p and + mid.getState() = state and + p.getPosition() = ppos and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc3 = mid.getSummaryCtx3() and + ap = mid.getAp() and + parameterMatch(ppos, apos) + ) + } + + pragma[nomagic] + private predicate revPartialPathThroughCallable0( + DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, + RevPartialAccessPath ap + ) { + exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | + revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _) and + revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap) + ) + } + + pragma[nomagic] + private predicate revPartialPathThroughCallable( + PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap + ) { + exists(DataFlowCall call, ArgumentPosition pos | + revPartialPathThroughCallable0(call, mid, pos, state, ap) and + node.asNode().(ArgNode).argumentOf(call, pos) + ) + } + + private predicate partialFlow(PartialPathNode source, PartialPathNode node) { + source.isFwdSource() and + node = source.getASuccessor+() + } + + private predicate revPartialFlow(PartialPathNode node, PartialPathNode sink) { + sink.isRevSink() and + node.getASuccessor+() = sink + } + + /** + * Holds if there is a partial data flow path from `source` to `node`. The + * approximate distance between `node` and the closest source is `dist` and + * is restricted to be less than or equal to `explorationLimit()`. This + * predicate completely disregards sink definitions. + * + * This predicate is intended for data-flow exploration and debugging and may + * perform poorly if the number of sources is too big and/or the exploration + * limit is set too high without using barriers. + * + * To use this in a `path-problem` query, import the module `PartialPathGraph`. + */ + predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { + partialFlow(source, node) and + dist = node.getSourceDistance() + } + + /** + * Holds if there is a partial data flow path from `node` to `sink`. The + * approximate distance between `node` and the closest sink is `dist` and + * is restricted to be less than or equal to `explorationLimit()`. This + * predicate completely disregards source definitions. + * + * This predicate is intended for data-flow exploration and debugging and may + * perform poorly if the number of sinks is too big and/or the exploration + * limit is set too high without using barriers. + * + * To use this in a `path-problem` query, import the module `PartialPathGraph`. + * + * Note that reverse flow has slightly lower precision than the corresponding + * forward flow, as reverse flow disregards type pruning among other features. + */ + predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { + revPartialFlow(node, sink) and + dist = node.getSinkDistance() + } } } - -import FlowExploration - -private predicate partialFlow( - PartialPathNode source, PartialPathNode node, Configuration configuration -) { - source.getConfiguration() = configuration and - source.isFwdSource() and - node = source.getASuccessor+() -} - -private predicate revPartialFlow( - PartialPathNode node, PartialPathNode sink, Configuration configuration -) { - sink.getConfiguration() = configuration and - sink.isRevSink() and - node.getASuccessor+() = sink -} diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl1.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl1.qll new file mode 100644 index 00000000000..e6bdc74cceb --- /dev/null +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl1.qll @@ -0,0 +1,396 @@ +/** + * DEPRECATED: Use `Make` and `MakeWithState` instead. + * + * Provides a `Configuration` class backwards-compatible interface to the data + * flow library. + */ + +private import DataFlowImplCommon +private import DataFlowImplSpecific::Private +import DataFlowImplSpecific::Public +private import DataFlowImpl +import DataFlowImplCommonPublic +import FlowStateString + +/** + * A configuration of interprocedural data flow analysis. This defines + * sources, sinks, and any other configurable aspect of the analysis. Each + * use of the global data flow library must define its own unique extension + * of this abstract class. To create a configuration, extend this class with + * a subclass whose characteristic predicate is a unique singleton string. + * For example, write + * + * ```ql + * class MyAnalysisConfiguration extends DataFlow::Configuration { + * MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" } + * // Override `isSource` and `isSink`. + * // Optionally override `isBarrier`. + * // Optionally override `isAdditionalFlowStep`. + * } + * ``` + * Conceptually, this defines a graph where the nodes are `DataFlow::Node`s and + * the edges are those data-flow steps that preserve the value of the node + * along with any additional edges defined by `isAdditionalFlowStep`. + * Specifying nodes in `isBarrier` will remove those nodes from the graph, and + * specifying nodes in `isBarrierIn` and/or `isBarrierOut` will remove in-going + * and/or out-going edges from those nodes, respectively. + * + * Then, to query whether there is flow between some `source` and `sink`, + * write + * + * ```ql + * exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink)) + * ``` + * + * Multiple configurations can coexist, but two classes extending + * `DataFlow::Configuration` should never depend on each other. One of them + * should instead depend on a `DataFlow2::Configuration`, a + * `DataFlow3::Configuration`, or a `DataFlow4::Configuration`. + */ +abstract class Configuration extends string { + bindingset[this] + Configuration() { any() } + + /** + * Holds if `source` is a relevant data flow source. + */ + predicate isSource(Node source) { none() } + + /** + * Holds if `source` is a relevant data flow source with the given initial + * `state`. + */ + predicate isSource(Node source, FlowState state) { none() } + + /** + * Holds if `sink` is a relevant data flow sink. + */ + predicate isSink(Node sink) { none() } + + /** + * Holds if `sink` is a relevant data flow sink accepting `state`. + */ + predicate isSink(Node sink, FlowState state) { none() } + + /** + * Holds if data flow through `node` is prohibited. This completely removes + * `node` from the data flow graph. + */ + predicate isBarrier(Node node) { none() } + + /** + * Holds if data flow through `node` is prohibited when the flow state is + * `state`. + */ + predicate isBarrier(Node node, FlowState state) { none() } + + /** Holds if data flow into `node` is prohibited. */ + predicate isBarrierIn(Node node) { none() } + + /** Holds if data flow out of `node` is prohibited. */ + predicate isBarrierOut(Node node) { none() } + + /** + * DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead. + * + * Holds if data flow through nodes guarded by `guard` is prohibited. + */ + deprecated predicate isBarrierGuard(BarrierGuard guard) { none() } + + /** + * DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead. + * + * Holds if data flow through nodes guarded by `guard` is prohibited when + * the flow state is `state` + */ + deprecated predicate isBarrierGuard(BarrierGuard guard, FlowState state) { none() } + + /** + * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. + */ + predicate isAdditionalFlowStep(Node node1, Node node2) { none() } + + /** + * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. + * This step is only applicable in `state1` and updates the flow state to `state2`. + */ + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + none() + } + + /** + * Holds if an arbitrary number of implicit read steps of content `c` may be + * taken at `node`. + */ + predicate allowImplicitRead(Node node, ContentSet c) { none() } + + /** + * Gets the virtual dispatch branching limit when calculating field flow. + * This can be overridden to a smaller value to improve performance (a + * value of 0 disables field flow), or a larger value to get more results. + */ + int fieldFlowBranchLimit() { result = 2 } + + /** + * Gets a data flow configuration feature to add restrictions to the set of + * valid flow paths. + * + * - `FeatureHasSourceCallContext`: + * Assume that sources have some existing call context to disallow + * conflicting return-flow directly following the source. + * - `FeatureHasSinkCallContext`: + * Assume that sinks have some existing call context to disallow + * conflicting argument-to-parameter flow directly preceding the sink. + * - `FeatureEqualSourceSinkCallContext`: + * Implies both of the above and additionally ensures that the entire flow + * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. + */ + FlowFeature getAFeature() { none() } + + /** Holds if sources should be grouped in the result of `hasFlowPath`. */ + predicate sourceGrouping(Node source, string sourceGroup) { none() } + + /** Holds if sinks should be grouped in the result of `hasFlowPath`. */ + predicate sinkGrouping(Node sink, string sinkGroup) { none() } + + /** + * Holds if data may flow from `source` to `sink` for this configuration. + */ + predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) } + + /** + * Holds if data may flow from `source` to `sink` for this configuration. + * + * The corresponding paths are generated from the end-points and the graph + * included in the module `PathGraph`. + */ + predicate hasFlowPath(PathNode source, PathNode sink) { hasFlowPath(source, sink, this) } + + /** + * Holds if data may flow from some source to `sink` for this configuration. + */ + predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) } + + /** + * Holds if data may flow from some source to `sink` for this configuration. + */ + predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } + + /** + * DEPRECATED: Use `FlowExploration` instead. + * + * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` + * measured in approximate number of interprocedural steps. + */ + deprecated int explorationLimit() { none() } + + /** + * Holds if hidden nodes should be included in the data flow graph. + * + * This feature should only be used for debugging or when the data flow graph + * is not visualized (for example in a `path-problem` query). + */ + predicate includeHiddenNodes() { none() } +} + +/** + * This class exists to prevent mutual recursion between the user-overridden + * member predicates of `Configuration` and the rest of the data-flow library. + * Good performance cannot be guaranteed in the presence of such recursion, so + * it should be replaced by using more than one copy of the data flow library. + */ +abstract private class ConfigurationRecursionPrevention extends Configuration { + bindingset[this] + ConfigurationRecursionPrevention() { any() } + + override predicate hasFlow(Node source, Node sink) { + strictcount(Node n | this.isSource(n)) < 0 + or + strictcount(Node n | this.isSource(n, _)) < 0 + or + strictcount(Node n | this.isSink(n)) < 0 + or + strictcount(Node n | this.isSink(n, _)) < 0 + or + strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0 + or + strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, _, n2, _)) < 0 + or + super.hasFlow(source, sink) + } +} + +/** A bridge class to access the deprecated `isBarrierGuard`. */ +private class BarrierGuardGuardedNodeBridge extends Unit { + abstract predicate guardedNode(Node n, Configuration config); + + abstract predicate guardedNode(Node n, FlowState state, Configuration config); +} + +private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { + deprecated override predicate guardedNode(Node n, Configuration config) { + exists(BarrierGuard g | + config.isBarrierGuard(g) and + n = g.getAGuardedNode() + ) + } + + deprecated override predicate guardedNode(Node n, FlowState state, Configuration config) { + exists(BarrierGuard g | + config.isBarrierGuard(g, state) and + n = g.getAGuardedNode() + ) + } +} + +private FlowState relevantState(Configuration config) { + config.isSource(_, result) or + config.isSink(_, result) or + config.isBarrier(_, result) or + config.isAdditionalFlowStep(_, result, _, _) or + config.isAdditionalFlowStep(_, _, _, result) +} + +private newtype TConfigState = + TMkConfigState(Configuration config, FlowState state) { + state = relevantState(config) or state instanceof FlowStateEmpty + } + +private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) } + +private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) } + +private predicate singleConfiguration() { 1 = strictcount(Configuration c) } + +private module Config implements FullStateConfigSig { + class FlowState = TConfigState; + + predicate isSource(Node source, FlowState state) { + getConfig(state).isSource(source, getState(state)) + or + getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty + } + + predicate isSink(Node sink, FlowState state) { + getConfig(state).isSink(sink, getState(state)) + or + getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty + } + + predicate isBarrier(Node node) { none() } + + predicate isBarrier(Node node, FlowState state) { + getConfig(state).isBarrier(node, getState(state)) or + getConfig(state).isBarrier(node) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state)) + } + + predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) } + + predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) } + + predicate isAdditionalFlowStep(Node node1, Node node2) { + singleConfiguration() and + any(Configuration config).isAdditionalFlowStep(node1, node2) + } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and + getConfig(state2) = getConfig(state1) + or + not singleConfiguration() and + getConfig(state1).isAdditionalFlowStep(node1, node2) and + state2 = state1 + } + + predicate allowImplicitRead(Node node, ContentSet c) { + any(Configuration config).allowImplicitRead(node, c) + } + + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } + + FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } + + predicate sourceGrouping(Node source, string sourceGroup) { + any(Configuration config).sourceGrouping(source, sourceGroup) + } + + predicate sinkGrouping(Node sink, string sinkGroup) { + any(Configuration config).sinkGrouping(sink, sinkGroup) + } + + predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() } +} + +private import Impl as I +import I + +/** + * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. + * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. + */ +class PathNode instanceof I::PathNode { + /** Gets a textual representation of this element. */ + final string toString() { result = super.toString() } + + /** + * Gets a textual representation of this element, including a textual + * representation of the call context. + */ + final string toStringWithContext() { result = super.toStringWithContext() } + + /** + * 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/). + */ + final predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + + /** Gets the underlying `Node`. */ + final Node getNode() { result = super.getNode() } + + /** Gets the `FlowState` of this node. */ + final FlowState getState() { result = getState(super.getState()) } + + /** Gets the associated configuration. */ + final Configuration getConfiguration() { result = getConfig(super.getState()) } + + /** Gets a successor of this node, if any. */ + final PathNode getASuccessor() { result = super.getASuccessor() } + + /** Holds if this node is a source. */ + final predicate isSource() { super.isSource() } + + /** Holds if this node is a grouping of source nodes. */ + final predicate isSourceGroup(string group) { super.isSourceGroup(group) } + + /** Holds if this node is a grouping of sink nodes. */ + final predicate isSinkGroup(string group) { super.isSinkGroup(group) } +} + +private predicate hasFlow(Node source, Node sink, Configuration config) { + exists(PathNode source0, PathNode sink0 | + hasFlowPath(source0, sink0, config) and + source0.getNode() = source and + sink0.getNode() = sink + ) +} + +private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) { + hasFlowPath(source, sink) and source.getConfiguration() = config +} + +private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) } + +predicate flowsTo = hasFlow/3; diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl2.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl2.qll index 1b969756b09..e6bdc74cceb 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl2.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl2.qll @@ -1,16 +1,16 @@ /** - * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from - * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed - * through the `Configuration` class. This file exists in several identical - * copies, allowing queries to use multiple `Configuration` classes that depend - * on each other without introducing mutual recursion among those configurations. + * DEPRECATED: Use `Make` and `MakeWithState` instead. + * + * Provides a `Configuration` class backwards-compatible interface to the data + * flow library. */ private import DataFlowImplCommon private import DataFlowImplSpecific::Private import DataFlowImplSpecific::Public +private import DataFlowImpl import DataFlowImplCommonPublic +import FlowStateString /** * A configuration of interprocedural data flow analysis. This defines @@ -144,6 +144,10 @@ abstract class Configuration extends string { * - `FeatureEqualSourceSinkCallContext`: * Implies both of the above and additionally ensures that the entire flow * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. */ FlowFeature getAFeature() { none() } @@ -156,7 +160,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from `source` to `sink` for this configuration. */ - predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } + predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) } /** * Holds if data may flow from `source` to `sink` for this configuration. @@ -169,9 +173,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from some source to `sink` for this configuration. */ - predicate hasFlowTo(Node sink) { - sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode() - } + predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) } /** * Holds if data may flow from some source to `sink` for this configuration. @@ -179,10 +181,12 @@ abstract class Configuration extends string { predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } /** + * DEPRECATED: Use `FlowExploration` instead. + * * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` * measured in approximate number of interprocedural steps. */ - int explorationLimit() { none() } + deprecated int explorationLimit() { none() } /** * Holds if hidden nodes should be included in the data flow graph. @@ -191,49 +195,6 @@ abstract class Configuration extends string { * is not visualized (for example in a `path-problem` query). */ predicate includeHiddenNodes() { none() } - - /** - * Holds if there is a partial data flow path from `source` to `node`. The - * approximate distance between `node` and the closest source is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards sink definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sources is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - */ - final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { - partialFlow(source, node, this) and - dist = node.getSourceDistance() - } - - /** - * Holds if there is a partial data flow path from `node` to `sink`. The - * approximate distance between `node` and the closest sink is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards source definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sinks is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - * - * Note that reverse flow has slightly lower precision than the corresponding - * forward flow, as reverse flow disregards type pruning among other features. - */ - final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { - revPartialFlow(node, sink, this) and - dist = node.getSinkDistance() - } } /** @@ -263,90 +224,6 @@ abstract private class ConfigurationRecursionPrevention extends Configuration { } } -private newtype TNodeEx = - TNodeNormal(Node n) or - TNodeImplicitRead(Node n, boolean hasRead) { - any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true] - } - -private class NodeEx extends TNodeEx { - string toString() { - result = this.asNode().toString() - or - exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") - } - - Node asNode() { this = TNodeNormal(result) } - - predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } - - Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } - - pragma[nomagic] - private DataFlowCallable getEnclosingCallable0() { - nodeEnclosingCallable(this.projectToNode(), result) - } - - pragma[inline] - DataFlowCallable getEnclosingCallable() { - pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) - } - - pragma[nomagic] - private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } - - pragma[inline] - DataFlowType getDataFlowType() { - pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) - } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -private class ArgNodeEx extends NodeEx { - ArgNodeEx() { this.asNode() instanceof ArgNode } -} - -private class ParamNodeEx extends NodeEx { - ParamNodeEx() { this.asNode() instanceof ParamNode } - - predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { - this.asNode().(ParamNode).isParameterOf(c, pos) - } - - ParameterPosition getPosition() { this.isParameterOf(_, result) } -} - -private class RetNodeEx extends NodeEx { - RetNodeEx() { this.asNode() instanceof ReturnNodeExt } - - ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } - - ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } -} - -private predicate inBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierIn(n) - | - config.isSource(n) or config.isSource(n, _) - ) -} - -private predicate outBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierOut(n) - | - config.isSink(n) or config.isSink(n, _) - ) -} - /** A bridge class to access the deprecated `isBarrierGuard`. */ private class BarrierGuardGuardedNodeBridge extends Unit { abstract predicate guardedNode(Node n, Configuration config); @@ -370,3019 +247,94 @@ private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { } } -pragma[nomagic] -private predicate fullBarrier(NodeEx node, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n) +private FlowState relevantState(Configuration config) { + config.isSource(_, result) or + config.isSink(_, result) or + config.isBarrier(_, result) or + config.isAdditionalFlowStep(_, result, _, _) or + config.isAdditionalFlowStep(_, _, _, result) +} + +private newtype TConfigState = + TMkConfigState(Configuration config, FlowState state) { + state = relevantState(config) or state instanceof FlowStateEmpty + } + +private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) } + +private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) } + +private predicate singleConfiguration() { 1 = strictcount(Configuration c) } + +private module Config implements FullStateConfigSig { + class FlowState = TConfigState; + + predicate isSource(Node source, FlowState state) { + getConfig(state).isSource(source, getState(state)) or - config.isBarrierIn(n) and - not config.isSource(n) and - not config.isSource(n, _) + getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty + } + + predicate isSink(Node sink, FlowState state) { + getConfig(state).isSink(sink, getState(state)) or - config.isBarrierOut(n) and - not config.isSink(n) and - not config.isSink(n, _) + getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty + } + + predicate isBarrier(Node node) { none() } + + predicate isBarrier(Node node, FlowState state) { + getConfig(state).isBarrier(node, getState(state)) or + getConfig(state).isBarrier(node) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state)) + } + + predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) } + + predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) } + + predicate isAdditionalFlowStep(Node node1, Node node2) { + singleConfiguration() and + any(Configuration config).isAdditionalFlowStep(node1, node2) + } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and + getConfig(state2) = getConfig(state1) or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config) - ) -} - -pragma[nomagic] -private predicate stateBarrier(NodeEx node, FlowState state, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n, state) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config) - ) -} - -pragma[nomagic] -private predicate sourceNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSource(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSource(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -pragma[nomagic] -private predicate sinkNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSink(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSink(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -/** Provides the relevant barriers for a step from `node1` to `node2`. */ -pragma[inline] -private predicate stepFilter(NodeEx node1, NodeEx node2, Configuration config) { - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) -} - -/** - * Holds if data can flow in one local step from `node1` to `node2`. - */ -private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.asNode() = n and - node2.isImplicitReadNode(n, false) and - not fullBarrier(node1, config) - ) -} - -/** - * Holds if the additional step from `node1` to `node2` does not jump between callables. - */ -private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.isImplicitReadNode(n, true) and - node2.asNode() = n and - not fullBarrier(node2, config) - ) -} - -private predicate additionalLocalStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) - ) -} - -/** - * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. - */ -private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if the additional step from `node1` to `node2` jumps between callables. - */ -private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate additionalJumpStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -pragma[nomagic] -private predicate readSet(NodeEx node1, ContentSet c, NodeEx node2, Configuration config) { - readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and - stepFilter(node1, node2, config) - or - exists(Node n | - node2.isImplicitReadNode(n, true) and - node1.isImplicitReadNode(n, _) and - config.allowImplicitRead(n, c) - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(ContentSet cs | - readSet(node1, cs, node2, config) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate clearsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - clearsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate expectsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - expectsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -pragma[nomagic] -private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } - -pragma[nomagic] -private predicate hasReadStep(Content c, Configuration config) { read(_, c, _, config) } - -pragma[nomagic] -private predicate store( - NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config -) { - store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), - contentType) and - hasReadStep(tc.getContent(), config) and - stepFilter(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { - viableReturnPosOut(call, pos, out.asNode()) -} - -pragma[nomagic] -private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { - viableParamArg(call, p.asNode(), arg.asNode()) -} - -/** - * Holds if field flow should be used for the given configuration. - */ -private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } - -private predicate hasSourceCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSourceCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate hasSinkCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSinkCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if flow from `p` to a return node of kind `kind` is allowed. - * - * We don't expect a parameter to return stored in itself, unless - * explicitly allowed - */ -bindingset[p, kind] -private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { - exists(ParameterPosition pos | p.isParameterOf(_, pos) | - not kind.(ParamUpdateReturnKind).getPosition() = pos - or - allowParameterReturnInSelfCached(p.asNode()) - ) -} - -private module Stage1 implements StageSig { - class Ap extends int { - // workaround for bad functionality-induced joins (happens when using `Unit`) - pragma[nomagic] - Ap() { this in [0 .. 1] and this < 1 } - } - - private class Cc = boolean; - - /* Begin: Stage 1 logic. */ - /** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `cc` records whether the node is reached through an - * argument in a call. - */ - private predicate fwdFlow(NodeEx node, Cc cc, Configuration config) { - sourceNode(node, _, config) and - if hasSourceCallCtx(config) then cc = true else cc = false - or - exists(NodeEx mid | fwdFlow(mid, cc, config) | - localFlowStep(mid, node, config) or - additionalLocalFlowStep(mid, node, config) or - additionalLocalStateStep(mid, _, node, _, config) - ) - or - exists(NodeEx mid | fwdFlow(mid, _, config) and cc = false | - jumpStep(mid, node, config) or - additionalJumpStep(mid, node, config) or - additionalJumpStateStep(mid, _, node, _, config) - ) - or - // store - exists(NodeEx mid | - useFieldFlow(config) and - fwdFlow(mid, cc, config) and - store(mid, _, node, _, config) - ) - or - // read - exists(ContentSet c | - fwdFlowReadSet(c, node, cc, config) and - fwdFlowConsCandSet(c, _, config) - ) - or - // flow into a callable - fwdFlowIn(_, _, _, node, config) and - cc = true - or - // flow out of a callable - fwdFlowOut(_, node, false, config) and - cc = false - or - // flow through a callable - exists(DataFlowCall call | - fwdFlowOutFromArg(call, node, config) and - fwdFlowIsEntered(call, cc, config) - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowIn( - DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p, Configuration config - ) { - // call context cannot help reduce virtual dispatch - fwdFlow(arg, cc, config) and - viableParamArgEx(call, p, arg) and - not fullBarrier(p, config) and - ( - cc = false - or - cc = true and - not reducedViableImplInCallContext(call, _, _) - ) - or - // call context may help reduce virtual dispatch - exists(DataFlowCallable target | - fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target, config) and - target = viableImplInSomeFwdFlowCallContextExt(call, config) and - cc = true - ) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - fwdFlowIn(call, _, cc, _, config) - } - - pragma[nomagic] - private predicate fwdFlowInReducedViableImplInSomeCallContext( - DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target, Configuration config - ) { - fwdFlow(arg, true, config) and - viableParamArgEx(call, p, arg) and - reducedViableImplInCallContext(call, _, _) and - target = p.getEnclosingCallable() and - not fullBarrier(p, config) - } - - /** - * Gets a viable dispatch target of `call` in the context `ctx`. This is - * restricted to those `call`s for which a context might make a difference, - * and to `ctx`s that are reachable in `fwdFlow`. - */ - pragma[nomagic] - private DataFlowCallable viableImplInSomeFwdFlowCallContextExt( - DataFlowCall call, Configuration config - ) { - exists(DataFlowCall ctx | - fwdFlowIsEntered(ctx, _, config) and - result = viableImplInCallContextExt(call, ctx) - ) - } - - private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) } - - pragma[nomagic] - private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc, Configuration config) { - exists(NodeEx mid | - fwdFlow(mid, cc, config) and - readSet(mid, c, node, config) - ) - } - - /** - * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - fwdFlow(mid, _, config) and - store(mid, tc, node, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `cs` may be interpreted in a read as the target of some store - * into `c`, in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCandSet(ContentSet cs, Content c, Configuration config) { - fwdFlowConsCand(c, config) and - c = cs.getAReadContent() - } - - pragma[nomagic] - private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { - exists(RetNodeEx ret | - fwdFlow(ret, cc, config) and - ret.getReturnPosition() = pos - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) { - exists(ReturnPosition pos | - fwdFlowReturnPosition(pos, cc, config) and - viableReturnPosOutEx(call, pos, out) and - not fullBarrier(out, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) { - fwdFlowOut(call, out, true, config) - } - - private predicate stateStepFwd(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1 | - additionalLocalStateStep(node1, state1, _, state2, config) or - additionalJumpStateStep(node1, state1, _, state2, config) - | - fwdFlow(node1, config) - ) - } - - private predicate fwdFlowState(FlowState state, Configuration config) { - sourceNode(_, state, config) - or - exists(FlowState state0 | - fwdFlowState(state0, config) and - stateStepFwd(state0, state, config) - ) - } - - /** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink. - */ - pragma[nomagic] - private predicate revFlow(NodeEx node, boolean toReturn, Configuration config) { - revFlow0(node, toReturn, config) and - fwdFlow(node, config) - } - - pragma[nomagic] - private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) { - exists(FlowState state | - fwdFlow(node, pragma[only_bind_into](config)) and - sinkNode(node, state, config) and - fwdFlowState(state, pragma[only_bind_into](config)) and - if hasSinkCallCtx(config) then toReturn = true else toReturn = false - ) - or - exists(NodeEx mid | revFlow(mid, toReturn, config) | - localFlowStep(node, mid, config) or - additionalLocalFlowStep(node, mid, config) or - additionalLocalStateStep(node, _, mid, _, config) - ) - or - exists(NodeEx mid | revFlow(mid, _, config) and toReturn = false | - jumpStep(node, mid, config) or - additionalJumpStep(node, mid, config) or - additionalJumpStateStep(node, _, mid, _, config) - ) - or - // store - exists(Content c | - revFlowStore(c, node, toReturn, config) and - revFlowConsCand(c, config) - ) - or - // read - exists(NodeEx mid, ContentSet c | - readSet(node, c, mid, config) and - fwdFlowConsCandSet(c, _, pragma[only_bind_into](config)) and - revFlow(mid, toReturn, pragma[only_bind_into](config)) - ) - or - // flow into a callable - revFlowIn(_, node, false, config) and - toReturn = false - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(pos, config) and - node.(RetNodeEx).getReturnPosition() = pos and - toReturn = true - ) - or - // flow through a callable - exists(DataFlowCall call | - revFlowInToReturn(call, node, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - /** - * Holds if `c` is the target of a read in the flow covered by `revFlow`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, ContentSet cs | - fwdFlow(node, pragma[only_bind_into](config)) and - readSet(node, cs, mid, config) and - fwdFlowConsCandSet(cs, c, pragma[only_bind_into](config)) and - revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) { - exists(NodeEx mid, TypedContent tc | - revFlow(mid, toReturn, pragma[only_bind_into](config)) and - fwdFlowConsCand(c, pragma[only_bind_into](config)) and - store(node, tc, mid, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `c` is the target of both a read and a store in the flow covered - * by `revFlow`. - */ - pragma[nomagic] - additional predicate revFlowIsReadAndStored(Content c, Configuration conf) { - revFlowConsCand(c, conf) and - revFlowStore(c, _, _, conf) - } - - pragma[nomagic] - additional predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config - ) { - fwdFlowReturnPosition(pos, _, config) and - viableReturnPosOutEx(call, pos, out) - } - - pragma[nomagic] - private predicate revFlowOut(ReturnPosition pos, Configuration config) { - exists(NodeEx out | - revFlow(out, _, config) and - viableReturnPosOutNodeCandFwd1(_, pos, out, config) - ) - } - - pragma[nomagic] - additional predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config - ) { - fwdFlowIn(call, arg, _, p, config) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate revFlowIn( - DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config - ) { - exists(ParamNodeEx p | - revFlow(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) { - revFlowIn(call, arg, true, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(NodeEx out | - revFlow(out, toReturn, config) and - fwdFlowOutFromArg(call, out, config) - ) - } - - private predicate stateStepRev(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - additionalLocalStateStep(node1, state1, node2, state2, config) or - additionalJumpStateStep(node1, state1, node2, state2, config) - | - revFlow(node1, _, pragma[only_bind_into](config)) and - revFlow(node2, _, pragma[only_bind_into](config)) and - fwdFlowState(state1, pragma[only_bind_into](config)) and - fwdFlowState(state2, pragma[only_bind_into](config)) - ) - } - - additional predicate revFlowState(FlowState state, Configuration config) { - exists(NodeEx node | - sinkNode(node, state, config) and - revFlow(node, _, pragma[only_bind_into](config)) and - fwdFlowState(state, pragma[only_bind_into](config)) - ) - or - exists(FlowState state0 | - revFlowState(state0, config) and - stateStepRev(state, state0, config) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Content c | - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - revFlow(node2, pragma[only_bind_into](config)) and - store(node1, tc, node2, contentType, config) and - c = tc.getContent() and - exists(ap1) - ) - } - - pragma[nomagic] - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) { - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - read(n1, c, n2, pragma[only_bind_into](config)) and - revFlow(n2, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, config) and - exists(ap) - } - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, _, pragma[only_bind_into](config)) and - exists(state) and - exists(ap) - } - - private predicate throughFlowNodeCand(NodeEx node, Configuration config) { - revFlow(node, true, config) and - fwdFlow(node, true, config) and - not inBarrier(node, config) and - not outBarrier(node, config) - } - - /** Holds if flow may return from `callable`. */ - pragma[nomagic] - private predicate returnFlowCallableNodeCand( - DataFlowCallable callable, ReturnKindExt kind, Configuration config - ) { - exists(RetNodeEx ret | - throughFlowNodeCand(ret, config) and - callable = ret.getEnclosingCallable() and - kind = ret.getKind() - ) - } - - /** - * Holds if flow may enter through `p` and reach a return node making `p` a - * candidate for the origin of a summary. - */ - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(DataFlowCallable c, ReturnKindExt kind | - throughFlowNodeCand(p, config) and - returnFlowCallableNodeCand(c, kind, config) and - p.getEnclosingCallable() = c and - exists(ap) and - parameterFlowThroughAllowed(p, kind) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - throughFlowNodeCand(ret, config) and - kind = ret.getKind() and - exists(argAp) and - exists(ap) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, boolean toReturn | - revFlow(arg, toReturn, config) and - revFlowInToReturn(call, arg, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, config)) and - fields = count(Content f0 | fwdFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | fwdFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, config)) and - fields = count(Content f0 | revFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | revFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | revFlow(n, b, config)) - } - /* End: Stage 1 logic. */ -} - -pragma[noinline] -private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - localFlowStep(node1, node2, config) -} - -pragma[noinline] -private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - additionalLocalFlowStep(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCand1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config -) { - Stage1::revFlow(out, config) and - Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, Configuration config -) { - exists(ReturnPosition pos | - viableReturnPosOutNodeCand1(call, pos, out, config) and - pos = ret.getReturnPosition() and - kind = pos.getKind() and - Stage1::revFlow(ret, config) and - not outBarrier(ret, config) and - not inBarrier(out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCand1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config -) { - Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and - Stage1::revFlow(arg, config) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config -) { - viableParamArgNodeCand1(call, p, arg, config) and - Stage1::revFlow(p, config) and - not outBarrier(arg, config) and - not inBarrier(p, config) -} - -/** - * Gets the amount of forward branching on the origin of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int branch(NodeEx n1, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n1, _, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) - ) -} - -/** - * Gets the amount of backward branching on the target of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int join(NodeEx n2, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n, _, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) - ) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. The - * `allowsFieldFlow` flag indicates whether the branching is within the limit - * specified by the configuration. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, ret, kind, out, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(ret, pragma[only_bind_into](config)) and - j = join(out, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. The `allowsFieldFlow` flag indicates whether - * the branching is within the limit specified by the configuration. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config -) { - flowIntoCallNodeCand1(call, arg, p, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(arg, pragma[only_bind_into](config)) and - j = join(p, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -private signature module StageSig { - class Ap; - - predicate revFlow(NodeEx node, Configuration config); - - predicate revFlowAp(NodeEx node, Ap ap, Configuration config); - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config); - - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config); - - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config); - - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ); - - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ); - - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config); -} - -private module MkStage { - class ApApprox = PrevStage::Ap; - - signature module StageParam { - class Ap; - - class ApNil extends Ap; - - bindingset[result, ap] - ApApprox getApprox(Ap ap); - - ApNil getApNil(NodeEx node); - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail); - - /** - * An approximation of `Content` that corresponds to the precision level of - * `Ap`, such that the mappings from both `Ap` and `Content` to this type - * are functional. - */ - class ApHeadContent; - - ApHeadContent getHeadContent(Ap ap); - - ApHeadContent projectToHeadContent(Content c); - - class ApOption; - - ApOption apNone(); - - ApOption apSome(Ap ap); - - class Cc; - - class CcCall extends Cc; - - // TODO: member predicate on CcCall - predicate matchesCall(CcCall cc, DataFlowCall call); - - class CcNoCall extends Cc; - - Cc ccNone(); - - CcCall ccSomeCall(); - - class LocalCc; - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc); - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ); - - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config - ); - - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config - ); - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config); - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType); - } - - module Stage implements StageSig { - import Param - - /* Begin: Stage logic. */ - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - private predicate revFlowApAlias(NodeEx node, ApApprox apa, Configuration config) { - PrevStage::revFlowAp(node, apa, config) - } - - pragma[nomagic] - private predicate flowIntoCallApa( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa, - Configuration config - ) { - flowIntoCall(call, arg, p, allowsFieldFlow, config) and - PrevStage::revFlowAp(p, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(arg, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowOutOfCallApa( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - ApApprox apa, Configuration config - ) { - flowOutOfCall(call, ret, kind, out, allowsFieldFlow, config) and - PrevStage::revFlowAp(out, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(ret, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowThroughOutOfCall( - DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, - ApApprox argApa, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa, pragma[only_bind_into](config)) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) and - matchesCall(ccc, call) - ) - } - - /** - * Holds if `node` is reachable with access path `ap` from a source in the - * configuration `config`. - * - * The call context `cc` records whether the node is reached through an - * argument in a call, and if so, `summaryCtx` and `argAp` record the - * corresponding parameter position and access path of that argument, respectively. - */ - pragma[nomagic] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa, config) and - PrevStage::revFlow(node, state, apa, config) and - filter(node, state, ap, config) - } - - pragma[inline] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, cc, summaryCtx, argAp, ap, _, config) - } - - pragma[nomagic] - private predicate fwdFlow0( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - sourceNode(node, state, config) and - (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and - argAp = apNone() and - summaryCtx = TParamNodeNone() and - ap = getApNil(node) and - apa = getApprox(ap) - or - exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | - fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0, config) and - localCc = getLocalCc(mid, cc) - | - localStep(mid, state0, node, state, true, _, config, localCc) and - ap = ap0 and - apa = apa0 - or - localStep(mid, state0, node, state, false, ap, config, localCc) and - ap0 instanceof ApNil and - apa = getApprox(ap) - ) - or - exists(NodeEx mid | - fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa, pragma[only_bind_into](config)) and - jumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(mid, state, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(mid, state0, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStateStep(mid, state0, node, state, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - // store - exists(TypedContent tc, Ap ap0 | - fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp, config) and - ap = apCons(tc, ap0) and - apa = getApprox(ap) - ) - or - // read - exists(Ap ap0, Content c | - fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp, config) and - fwdFlowConsCand(ap0, c, ap, config) and - apa = getApprox(ap) - ) - or - // flow into a callable - fwdFlowIn(_, node, state, _, cc, _, _, ap, apa, config) and - if PrevStage::parameterMayFlowThrough(node, apa, config) - then ( - summaryCtx = TParamNodeSome(node.asNode()) and - argAp = apSome(ap) - ) else ( - summaryCtx = TParamNodeNone() and argAp = apNone() - ) - or - // flow out of a callable - exists( - DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, - DataFlowCallable inner - | - fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa, config) and - flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa, config) and - inner = ret.getEnclosingCallable() and - cc = getCallContextReturn(inner, call, innercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - or - // flow through a callable - exists( - DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, ApApprox innerArgApa - | - fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowStore( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(DataFlowType contentType, ApApprox apa1 | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1, config) and - PrevStage::storeStepCand(node1, apa1, tc, node2, contentType, config) and - typecheckStore(ap1, contentType) - ) - } - - /** - * Holds if forward flow with access path `tail` reaches a store of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(TypedContent tc | - fwdFlowStore(_, tail, tc, _, _, _, _, _, config) and - tc.getContent() = c and - cons = apCons(tc, tail) - ) - } - - pragma[nomagic] - private predicate readStepCand( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - PrevStage::readStepCand(node1, c, node2, config) and - apc = projectToHeadContent(c) - } - - bindingset[node1, apc] - pragma[inline_late] - private predicate readStepCand0( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - readStepCand(node1, apc, c, node2, config) - } - - pragma[nomagic] - private predicate fwdFlowRead( - Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(ApHeadContent apc | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap, config) and - apc = getHeadContent(ap) and - readStepCand0(node1, apc, c, node2, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowIn( - DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, - ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa, Configuration config - ) { - exists(ArgNodeEx arg, boolean allowsFieldFlow | - fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa, config) and - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowRetFromArg( - RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, ApApprox argApa, - Ap ap, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - fwdFlow(pragma[only_bind_into](ret), state, ccc, - TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), - pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa), - pragma[only_bind_into](config)) and - kind = ret.getKind() and - parameterFlowThroughAllowed(summaryCtx, kind) and - argApa = getApprox(argAp) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) - ) - } - - pragma[inline] - private predicate fwdFlowThrough0( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, - Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa, config) and - fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp, config) - } - - pragma[nomagic] - private predicate fwdFlowThrough( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa, - config) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` - * and data might flow through the target callable and back out at `call`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered( - DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, - ParamNodeEx p, Ap ap, Configuration config - ) { - exists(ApApprox apa | - fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(p, apa, config) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate storeStepFwd( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config - ) { - fwdFlowStore(node1, ap1, tc, node2, _, _, _, _, config) and - ap2 = apCons(tc, ap1) and - fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _, config) - } - - private predicate readStepFwd( - NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config - ) { - fwdFlowRead(ap1, c, n1, n2, _, _, _, _, config) and - fwdFlowConsCand(ap1, c, ap2, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough0( - DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, - ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, - innerArgApa, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough( - RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, - Ap ap, Configuration config - ) { - exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | - returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa, config) and - pos = ret.getReturnPosition() and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowThroughIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap, - Configuration config - ) { - exists(ApApprox argApa | - flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), - allowsFieldFlow, argApa, pragma[only_bind_into](config)) and - fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa, - pragma[only_bind_into](config)) and - returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap, - pragma[only_bind_into](config)) and - if allowsFieldFlow = false then argAp instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowIntoCallAp( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap, - Configuration config - ) { - exists(ApApprox apa | - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - fwdFlow(arg, _, _, _, _, ap, apa, config) - ) - } - - pragma[nomagic] - private predicate flowOutOfCallAp( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, - Ap ap, Configuration config - ) { - exists(ApApprox apa | - flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa, config) and - fwdFlow(ret, _, _, _, _, ap, apa, config) and - pos = ret.getReturnPosition() - ) - } - - /** - * Holds if `node` with access path `ap` is part of a path from a source to a - * sink in the configuration `config`. - * - * The parameter `returnCtx` records whether (and how) the node must be returned - * from the enclosing callable in order to reach a sink, and if so, `returnAp` - * records the access path of the returned value. - */ - pragma[nomagic] - additional predicate revFlow( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - revFlow0(node, state, returnCtx, returnAp, ap, config) and - fwdFlow(node, state, _, _, _, ap, config) - } - - pragma[nomagic] - private predicate revFlow0( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, _, _, _, ap, config) and - sinkNode(node, state, config) and - ( - if hasSinkCallCtx(config) - then returnCtx = TReturnCtxNoFlowThrough() - else returnCtx = TReturnCtxNone() - ) and - returnAp = apNone() and - ap instanceof ApNil - or - exists(NodeEx mid, FlowState state0 | - localStep(node, state, mid, state0, true, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, ap, config) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap, pragma[only_bind_into](config)) and - localStep(node, pragma[only_bind_into](state), mid, state0, false, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, nil, pragma[only_bind_into](config)) and - ap instanceof ApNil - ) - or - exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, state, _, _, ap, config) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStep(node, mid, config) and - revFlow(pragma[only_bind_into](mid), state, _, _, nil, pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStateStep(node, state, mid, state0, config) and - revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil, - pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - // store - exists(Ap ap0, Content c | - revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp, config) and - revFlowConsCand(ap0, c, ap, config) - ) - or - // read - exists(NodeEx mid, Ap ap0 | - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - readStepFwd(node, ap, _, mid, ap0, config) - ) - or - // flow into a callable - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, state, TReturnCtxNone(), returnAp, ap, config) and - flowIntoCallAp(_, node, p, allowsFieldFlow, ap, config) and - (if allowsFieldFlow = false then ap instanceof ApNil else any()) and - returnCtx = TReturnCtxNone() - ) - or - // flow through a callable - exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, node, p, _, ap, innerReturnAp, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(_, node, pos, state, _, _, ap, config) and - if returnFlowsThrough(node, pos, state, _, _, _, ap, config) - then ( - returnCtx = TReturnCtxMaybeFlowThrough(pos) and - returnAp = apSome(ap) - ) else ( - returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() - ) - ) - } - - pragma[nomagic] - private predicate revFlowStore( - Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, - ReturnCtx returnCtx, ApOption returnAp, Configuration config - ) { - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - storeStepFwd(node, ap, tc, mid, ap0, config) and - tc.getContent() = c - } - - /** - * Holds if reverse flow with access path `tail` reaches a read of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(NodeEx mid, Ap tail0 | - revFlow(mid, _, _, _, tail, config) and - tail = pragma[only_bind_into](tail0) and - readStepFwd(_, cons, c, mid, tail0, config) - ) - } - - pragma[nomagic] - private predicate revFlowOut( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, - ApOption returnAp, Ap ap, Configuration config - ) { - exists(NodeEx out, boolean allowsFieldFlow | - revFlow(out, state, returnCtx, returnAp, ap, config) and - flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate revFlowParamToReturn( - ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap, Configuration config - ) { - revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), - pragma[only_bind_into](ap), pragma[only_bind_into](config)) and - parameterFlowThroughAllowed(p, pos.getKind()) and - PrevStage::parameterMayFlowThrough(p, getApprox(ap), config) - } - - pragma[nomagic] - private predicate revFlowThrough( - DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, - ApOption returnAp, Ap ap, Ap innerReturnAp, Configuration config - ) { - revFlowParamToReturn(p, state, pos, innerReturnAp, ap, config) and - revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned( - DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap, - Configuration config - ) { - exists(RetNodeEx ret, FlowState state, CcCall ccc | - revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap, config) and - returnFlowsThrough(ret, pos, state, ccc, _, _, ap, config) and - matchesCall(ccc, call) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Ap ap2, Content c | - PrevStage::storeStepCand(node1, _, tc, node2, contentType, config) and - revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _, config) and - revFlowConsCand(ap2, c, ap1, config) - ) - } - - predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(Ap ap1, Ap ap2 | - revFlow(node2, _, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and - readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _, - pragma[only_bind_into](config)) - ) - } - - additional predicate revFlow(NodeEx node, FlowState state, Configuration config) { - revFlow(node, state, _, _, _, config) - } - - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, _, _, ap, config) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, _, _, _, ap, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, Configuration config) { - revFlow(node, _, _, _, _, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, ap, config) - } - - private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepFwd(_, ap, tc, _, _, config) - } - - private predicate revConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepCand(_, ap, tc, _, _, config) - } - - private predicate validAp(Ap ap, Configuration config) { - revFlow(_, _, _, _, ap, config) and ap instanceof ApNil - or - exists(TypedContent head, Ap tail | - consCand(head, tail, config) and - ap = apCons(head, tail) - ) - } - - additional predicate consCand(TypedContent tc, Ap ap, Configuration config) { - revConsCand(tc, ap, config) and - validAp(ap, config) - } - - pragma[nomagic] - private predicate parameterFlowsThroughRev( - ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp, Configuration config - ) { - revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap, config) and - parameterFlowThroughAllowed(p, pos.getKind()) - } - - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(ReturnPosition pos | - returnFlowsThrough(_, pos, _, _, p, ap, _, config) and - parameterFlowsThroughRev(p, ap, pos, _, config) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - exists(ParamNodeEx p, ReturnPosition pos | - returnFlowsThrough(ret, pos, _, _, p, argAp, ap, config) and - parameterFlowsThroughRev(p, argAp, pos, ap, config) and - kind = pos.getKind() - ) - } - - pragma[nomagic] - private predicate revFlowThroughArg( - DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, - Ap ap, Configuration config - ) { - exists(ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp, config) - ) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | - revFlow(arg, state, returnCtx, returnAp, ap, config) and - revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, config)) and - fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and - states = count(FlowState state | fwdFlow(_, state, _, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | - fwdFlow(n, state, cc, summaryCtx, argAp, ap, config) - ) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, _, _, _, config)) and - fields = count(TypedContent f0 | consCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and - states = count(FlowState state | revFlow(_, state, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | - revFlow(n, state, returnCtx, retAp, ap, config) - ) - } - /* End: Stage logic. */ - } -} - -private module BooleanCallContext { - class Cc extends boolean { - Cc() { this in [true, false] } - } - - class CcCall extends Cc { - CcCall() { this = true } - } - - /** Holds if the call context may be `call`. */ - predicate matchesCall(CcCall cc, DataFlowCall call) { any() } - - class CcNoCall extends Cc { - CcNoCall() { this = false } - } - - Cc ccNone() { result = false } - - CcCall ccSomeCall() { result = true } - - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } -} - -private module Level1CallContext { - class Cc = CallContext; - - class CcCall = CallContextCall; - - pragma[inline] - predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } - - class CcNoCall = CallContextNoCall; - - Cc ccNone() { result instanceof CallContextAny } - - CcCall ccSomeCall() { result instanceof CallContextSomeCall } - - module NoLocalCallContext { - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSiteDispatch(call, c) - then result = TSpecificCall(call) - else result = TSomeCall() - } - } - - module LocalCallContext { - class LocalCc = LocalCallContext; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { - result = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - node.getEnclosingCallable()) - } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() - } - } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { - checkCallContextReturn(innercc, c, call) and - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() - } -} - -private module Stage2Param implements MkStage::StageParam { - private module PrevStage = Stage1; - - class Ap extends boolean { - Ap() { this in [true, false] } - } - - class ApNil extends Ap { - ApNil() { this = false } - } - - bindingset[result, ap] - PrevStage::Ap getApprox(Ap ap) { any() } - - ApNil getApNil(NodeEx node) { Stage1::revFlow(node, _) and exists(result) } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } - - class ApHeadContent = Unit; - - pragma[inline] - ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } - - ApHeadContent projectToHeadContent(Content c) { any() } - - class ApOption = BooleanOption; - - ApOption apNone() { result = TBooleanNone() } - - ApOption apSome(Ap ap) { result = TBooleanSome(ap) } - - import Level1CallContext - import NoLocalCallContext - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - ( - preservesValue = true and - localFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalStateStep(node1, state1, node2, state2, config) - ) and - exists(ap) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand1/6; - - predicate flowIntoCall = flowIntoCallNodeCand1/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - expectsContentEx(node, c) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - PrevStage::revFlowState(state, pragma[only_bind_into](config)) and - exists(ap) and - not stateBarrier(node, state, config) and - ( - notExpectsContent(node) - or - ap = true and - expectsContentCand(node, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage2 implements StageSig { - import MkStage::Stage -} - -pragma[nomagic] -private predicate flowOutOfCallNodeCand2( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -pragma[nomagic] -private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -private module LocalFlowBigStep { - /** - * A node where some checking is required, and hence the big-step relation - * is not allowed to step over. - */ - private class FlowCheckNode extends NodeEx { - FlowCheckNode() { - castNode(this.asNode()) or - clearsContentCached(this.asNode(), _) or - expectsContentCached(this.asNode(), _) - } - } - - /** - * Holds if `node` can be the first node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowEntry(NodeEx node, FlowState state, Configuration config) { - Stage2::revFlow(node, state, config) and - ( - sourceNode(node, state, config) - or - jumpStep(_, node, config) - or - additionalJumpStep(_, node, config) - or - additionalJumpStateStep(_, _, node, state, config) - or - node instanceof ParamNodeEx - or - node.asNode() instanceof OutNodeExt - or - Stage2::storeStepCand(_, _, _, node, _, config) - or - Stage2::readStepCand(_, _, node, config) - or - node instanceof FlowCheckNode - or - exists(FlowState s | - additionalLocalStateStep(_, s, node, state, config) and - s != state - ) - ) - } - - /** - * Holds if `node` can be the last node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowExit(NodeEx node, FlowState state, Configuration config) { - exists(NodeEx next | Stage2::revFlow(next, state, config) | - jumpStep(node, next, config) or - additionalJumpStep(node, next, config) or - flowIntoCallNodeCand2(_, node, next, _, config) or - flowOutOfCallNodeCand2(_, node, _, next, _, config) or - Stage2::storeStepCand(node, _, _, next, _, config) or - Stage2::readStepCand(node, _, next, config) - ) - or - exists(NodeEx next, FlowState s | Stage2::revFlow(next, s, config) | - additionalJumpStateStep(node, state, next, s, config) - or - additionalLocalStateStep(node, state, next, s, config) and - s != state - ) - or - Stage2::revFlow(node, state, config) and - node instanceof FlowCheckNode - or - sinkNode(node, state, config) - } - - pragma[noinline] - private predicate additionalLocalFlowStepNodeCand2( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, Configuration config - ) { - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 and - Stage2::revFlow(node1, pragma[only_bind_into](state1), false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false, - pragma[only_bind_into](config)) - or - additionalLocalStateStep(node1, state1, node2, state2, config) and - Stage2::revFlow(node1, state1, false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, state2, false, pragma[only_bind_into](config)) - } - - /** - * Holds if the local path from `node1` to `node2` is a prefix of a maximal - * subsequence of local flow steps in a dataflow path. - * - * This is the transitive closure of `[additional]localFlowStep` beginning - * at `localFlowEntry`. - */ - pragma[nomagic] - private predicate localFlowStepPlus( - NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, - Configuration config, LocalCallContext cc - ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and - ( - localFlowEntry(node1, pragma[only_bind_into](state), pragma[only_bind_into](config)) and - ( - localFlowStepNodeCand1(node1, node2, config) and - preservesValue = true and - t = node1.getDataFlowType() and // irrelevant dummy value - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - or - additionalLocalFlowStepNodeCand2(node1, state, node2, state, config) and - preservesValue = false and - t = node2.getDataFlowType() - ) and - node1 != node2 and - cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) - or - exists(NodeEx mid | - localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, - pragma[only_bind_into](config), cc) and - localFlowStepNodeCand1(mid, node2, config) and - not mid instanceof FlowCheckNode and - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - ) - or - exists(NodeEx mid | - localFlowStepPlus(node1, state, mid, _, _, pragma[only_bind_into](config), cc) and - additionalLocalFlowStepNodeCand2(mid, state, node2, state, config) and - not mid instanceof FlowCheckNode and - preservesValue = false and - t = node2.getDataFlowType() - ) - ) - } - - /** - * Holds if `node1` can step to `node2` in one or more local steps and this - * path can occur as a maximal subsequence of local steps in a dataflow path. - */ - pragma[nomagic] - predicate localFlowBigStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - DataFlowType t, Configuration config, LocalCallContext callContext - ) { - localFlowStepPlus(node1, state1, node2, preservesValue, t, config, callContext) and - localFlowExit(node2, state1, config) and - state1 = state2 - or - additionalLocalFlowStepNodeCand2(node1, state1, node2, state2, config) and - state1 != state2 and - preservesValue = false and - t = node2.getDataFlowType() and - callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) - } -} - -private import LocalFlowBigStep - -private module Stage3Param implements MkStage::StageParam { - private module PrevStage = Stage2; - - class Ap = ApproxAccessPathFront; - - class ApNil = ApproxAccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } - - class ApHeadContent = ContentApprox; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - predicate projectToHeadContent = getContentApprox/1; - - class ApOption = ApproxAccessPathFrontOption; - - ApOption apNone() { result = TApproxAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } - - import BooleanCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApproxAccessPathFrontNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand2/6; - - predicate flowIntoCall = flowIntoCallNodeCand2/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getAHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage3 implements StageSig { - import MkStage::Stage -} - -private module Stage4Param implements MkStage::StageParam { - private module PrevStage = Stage3; - - class Ap = AccessPathFront; - - class ApNil = AccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathFrontOption; - - ApOption apNone() { result = TAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } - - import BooleanCallContext - - pragma[nomagic] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) and - exists(lcc) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clearSet(NodeEx node, ContentSet c, Configuration config) { - PrevStage::revFlow(node, config) and - clearsContentCached(node.asNode(), c) - } - - pragma[nomagic] - private predicate clearContent(NodeEx node, Content c, Configuration config) { - exists(ContentSet cs | - PrevStage::readStepCand(_, pragma[only_bind_into](c), _, pragma[only_bind_into](config)) and - c = cs.getAReadContent() and - clearSet(node, cs, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clear(NodeEx node, Ap ap, Configuration config) { - clearContent(node, ap.getHead().getContent(), config) - } - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - not clear(node, ap, config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage4 implements StageSig { - import MkStage::Stage -} - -/** - * Holds if `argApf` is recorded as the summary context for flow reaching `node` - * and remains relevant for the following pruning stage. - */ -private predicate flowCandSummaryCtx( - NodeEx node, FlowState state, AccessPathFront argApf, Configuration config -) { - exists(AccessPathFront apf | - Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf, config) and - Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf, - config) - ) -} - -/** - * Holds if a length 2 access path approximation with the head `tc` is expected - * to be expensive. - */ -private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { - exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf, config)) and - nodes = - strictcount(NodeEx n, FlowState state | - Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - or - flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - ) and - accessPathApproxCostLimits(apLimit, tupleLimit) and - apLimit < tails and - tupleLimit < (tails - 1) * nodes and - not tc.forceHighPrecision() - ) -} - -private newtype TAccessPathApprox = - TNil(DataFlowType t) or - TConsNil(TypedContent tc, DataFlowType t) { - Stage4::consCand(tc, TFrontNil(t), _) and - not expensiveLen2unfolding(tc, _) - } or - TConsCons(TypedContent tc1, TypedContent tc2, int len) { - Stage4::consCand(tc1, TFrontHead(tc2), _) and - len in [2 .. accessPathLimit()] and - not expensiveLen2unfolding(tc1, _) - } or - TCons1(TypedContent tc, int len) { - len in [1 .. accessPathLimit()] and - expensiveLen2unfolding(tc, _) - } - -/** - * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only - * the first two elements of the list and its length are tracked. If data flows - * from a source to a given node with a given `AccessPathApprox`, this indicates - * the sequence of dereference operations needed to get from the value in the node - * to the tracked object. The final type indicates the type of the tracked object. - */ -abstract private class AccessPathApprox extends TAccessPathApprox { - abstract string toString(); - - abstract TypedContent getHead(); - - abstract int len(); - - abstract DataFlowType getType(); - - abstract AccessPathFront getFront(); - - /** Gets the access path obtained by popping `head` from this path, if any. */ - abstract AccessPathApprox pop(TypedContent head); -} - -private class AccessPathApproxNil extends AccessPathApprox, TNil { - private DataFlowType t; - - AccessPathApproxNil() { this = TNil(t) } - - override string toString() { result = concat(": " + ppReprType(t)) } - - override TypedContent getHead() { none() } - - override int len() { result = 0 } - - override DataFlowType getType() { result = t } - - override AccessPathFront getFront() { result = TFrontNil(t) } - - override AccessPathApprox pop(TypedContent head) { none() } -} - -abstract private class AccessPathApproxCons extends AccessPathApprox { } - -private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { - private TypedContent tc; - private DataFlowType t; - - AccessPathApproxConsNil() { this = TConsNil(tc, t) } - - override string toString() { - // The `concat` becomes "" if `ppReprType` has no result. - result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) - } - - override TypedContent getHead() { result = tc } - - override int len() { result = 1 } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } -} - -private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { - private TypedContent tc1; - private TypedContent tc2; - private int len; - - AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - - override string toString() { - if len = 2 - then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" - else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc1 } - - override int len() { result = len } - - override DataFlowType getType() { result = tc1.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc1) } - - override AccessPathApprox pop(TypedContent head) { - head = tc1 and - ( - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - } -} - -private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { - private TypedContent tc; - private int len; - - AccessPathApproxCons1() { this = TCons1(tc, len) } - - override string toString() { - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc } - - override int len() { result = len } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { - head = tc and - ( - exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2), _) | - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - or - exists(DataFlowType t | - len = 1 and - Stage4::consCand(tc, TFrontNil(t), _) and - result = TNil(t) - ) - ) - } -} - -/** Gets the access path obtained by popping `tc` from `ap`, if any. */ -private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } - -/** Gets the access path obtained by pushing `tc` onto `ap`. */ -private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } - -private newtype TAccessPathApproxOption = - TAccessPathApproxNone() or - TAccessPathApproxSome(AccessPathApprox apa) - -private class AccessPathApproxOption extends TAccessPathApproxOption { - string toString() { - this = TAccessPathApproxNone() and result = "" - or - this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) - } -} - -private module Stage5Param implements MkStage::StageParam { - private module PrevStage = Stage4; - - class Ap = AccessPathApprox; - - class ApNil = AccessPathApproxNil; - - pragma[nomagic] - PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathApproxOption; - - ApOption apNone() { result = TAccessPathApproxNone() } - - ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } - - import Level1CallContext - import LocalCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, lcc) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { any() } - - // Type checking is not necessary here as it has already been done in stage 3. - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage5 = MkStage::Stage; - -bindingset[conf, result] -private Configuration unbindConf(Configuration conf) { - exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c)) -} - -pragma[nomagic] -private predicate nodeMayUseSummary0( - NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(AccessPathApprox apa0 | - Stage5::parameterMayFlowThrough(p, _, _) and - Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0, config) and - Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), - TAccessPathApproxSome(apa), apa0, config) - ) -} - -pragma[nomagic] -private predicate nodeMayUseSummary( - NodeEx n, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::parameterMayFlowThrough(p, apa, config) and - nodeMayUseSummary0(n, p, state, apa, config) - ) -} - -private newtype TSummaryCtx = - TSummaryCtxNone() or - TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { - exists(Configuration config | - Stage5::parameterMayFlowThrough(p, ap.getApprox(), config) and - Stage5::revFlow(p, state, _, config) - ) - } - -/** - * A context for generating flow summaries. This represents flow entry through - * a specific parameter with an access path of a specific shape. - * - * Summaries are only created for parameters that may flow through. - */ -abstract private class SummaryCtx extends TSummaryCtx { - abstract string toString(); -} - -/** A summary context from which no flow summary can be generated. */ -private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { - override string toString() { result = "" } -} - -/** A summary context from which a flow summary can be generated. */ -private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParamNodeEx p; - private FlowState s; - private AccessPath ap; - - SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } - - ParameterPosition getParameterPos() { p.isParameterOf(_, result) } - - ParamNodeEx getParamNode() { result = p } - - override string toString() { result = p + ": " + ap } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** - * Gets the number of length 2 access path approximations that correspond to `apa`. - */ -private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { - exists(TypedContent tc, int len | - tc = apa.getHead() and - len = apa.len() and - result = - strictcount(AccessPathFront apf | - Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), - config) - ) - ) -} - -private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = - strictcount(NodeEx n, FlowState state | - Stage5::revFlow(n, state, apa, config) or nodeMayUseSummary(n, state, apa, config) - ) -} - -/** - * Holds if a length 2 access path approximation matching `apa` is expected - * to be expensive. - */ -private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = count1to2unfold(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - apLimit < aps and - tupleLimit < (aps - 1) * nodes - ) -} - -private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { - exists(TypedContent head | - apa.pop(head) = result and - Stage5::consCand(head, result, config) - ) -} - -/** - * Holds with `unfold = false` if a precise head-tail representation of `apa` is - * expected to be expensive. Holds with `unfold = true` otherwise. - */ -private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { - if apa.getHead().forceHighPrecision() - then unfold = true - else - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = countPotentialAps(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true - ) -} - -/** - * Gets the number of `AccessPath`s that correspond to `apa`. - */ -pragma[assume_small_delta] -private int countAps(AccessPathApprox apa, Configuration config) { - evalUnfold(apa, false, config) and - result = 1 and - (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) - or - evalUnfold(apa, false, config) and - result = count1to2unfold(apa, config) and - not expensiveLen1to2unfolding(apa, config) - or - evalUnfold(apa, true, config) and - result = countPotentialAps(apa, config) -} - -/** - * Gets the number of `AccessPath`s that would correspond to `apa` assuming - * that it is expanded to a precise head-tail representation. - */ -language[monotonicAggregates] -pragma[assume_small_delta] -private int countPotentialAps(AccessPathApprox apa, Configuration config) { - apa instanceof AccessPathApproxNil and result = 1 - or - result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) -} - -private newtype TAccessPath = - TAccessPathNil(DataFlowType t) or - TAccessPathCons(TypedContent head, AccessPath tail) { - exists(AccessPathApproxCons apa | - not evalUnfold(apa, false, _) and - head = apa.getHead() and - tail.getApprox() = getATail(apa, _) - ) - } or - TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - not expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head1 = apa.getHead() and - head2 = getATail(apa, _).getHead() - ) - } or - TAccessPathCons1(TypedContent head, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head = apa.getHead() - ) - } - -private newtype TPathNode = - pragma[assume_small_delta] - TPathNodeMid( - NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config - ) { - // A PathNode is introduced by a source ... - Stage5::revFlow(node, state, config) and - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - or - // ... or a step from an existing PathNode to another node. - exists(PathNodeMid mid | - pathStep(mid, node, state, cc, sc, ap) and - pragma[only_bind_into](config) = mid.getConfiguration() and - Stage5::revFlow(node, state, ap.getApprox(), pragma[only_bind_into](config)) - ) - } or - TPathNodeSink(NodeEx node, FlowState state, Configuration config) { - exists(PathNodeMid sink | - sink.isAtSink() and - node = sink.getNodeEx() and - state = sink.getState() and - config = sink.getConfiguration() - ) - } or - TPathNodeSourceGroup(string sourceGroup, Configuration config) { - exists(PathNodeImpl source | - sourceGroup = source.getSourceGroup() and - config = source.getConfiguration() - ) - } or - TPathNodeSinkGroup(string sinkGroup, Configuration config) { - exists(PathNodeSink sink | - sinkGroup = sink.getSinkGroup() and - config = sink.getConfiguration() - ) - } - -/** - * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a - * source to a given node with a given `AccessPath`, this indicates the sequence - * of dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ -private class AccessPath extends TAccessPath { - /** Gets the head of this access path, if any. */ - abstract TypedContent getHead(); - - /** Gets the tail of this access path, if any. */ - abstract AccessPath getTail(); - - /** Gets the front of this access path. */ - abstract AccessPathFront getFront(); - - /** Gets the approximation of this access path. */ - abstract AccessPathApprox getApprox(); - - /** Gets the length of this access path. */ - abstract int length(); - - /** Gets a textual representation of this access path. */ - abstract string toString(); - - /** Gets the access path obtained by popping `tc` from this access path, if any. */ - final AccessPath pop(TypedContent tc) { - result = this.getTail() and - tc = this.getHead() - } - - /** Gets the access path obtained by pushing `tc` onto this access path. */ - final AccessPath push(TypedContent tc) { this = result.pop(tc) } -} - -private class AccessPathNil extends AccessPath, TAccessPathNil { - private DataFlowType t; - - AccessPathNil() { this = TAccessPathNil(t) } - - DataFlowType getType() { result = t } - - override TypedContent getHead() { none() } - - override AccessPath getTail() { none() } - - override AccessPathFrontNil getFront() { result = TFrontNil(t) } - - override AccessPathApproxNil getApprox() { result = TNil(t) } - - override int length() { result = 0 } - - override string toString() { result = concat(": " + ppReprType(t)) } -} - -private class AccessPathCons extends AccessPath, TAccessPathCons { - private TypedContent head; - private AccessPath tail; - - AccessPathCons() { this = TAccessPathCons(head, tail) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { result = tail } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - pragma[assume_small_delta] - override AccessPathApproxCons getApprox() { - result = TConsNil(head, tail.(AccessPathNil).getType()) - or - result = TConsCons(head, tail.getHead(), this.length()) - or - result = TCons1(head, this.length()) - } - - pragma[assume_small_delta] - override int length() { result = 1 + tail.length() } - - private string toStringImpl(boolean needsSuffix) { - exists(DataFlowType t | - tail = TAccessPathNil(t) and - needsSuffix = false and - result = head.toString() + "]" + concat(" : " + ppReprType(t)) - ) - or - result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) - or - exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | - result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true - or - result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false - ) - or - exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | - result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true - or - result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false - ) - } - - override string toString() { - result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" - or - result = "[" + this.toStringImpl(false) - } -} - -private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { - private TypedContent head1; - private TypedContent head2; - private int len; - - AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } - - override TypedContent getHead() { result = head1 } - - override AccessPath getTail() { - Stage5::consCand(head1, result.getApprox(), _) and - result.getHead() = head2 and - result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head1) } - - override AccessPathApproxCons getApprox() { - result = TConsCons(head1, head2, len) or - result = TCons1(head1, len) - } - - override int length() { result = len } - - override string toString() { - if len = 2 - then result = "[" + head1.toString() + ", " + head2.toString() + "]" - else - result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" - } -} - -private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { - private TypedContent head; - private int len; - - AccessPathCons1() { this = TAccessPathCons1(head, len) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { - Stage5::consCand(head, result.getApprox(), _) and result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - - override int length() { result = len } - - override string toString() { - if len = 1 - then result = "[" + head.toString() + "]" - else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" - } -} - -abstract private class PathNodeImpl extends TPathNode { - /** Gets the `FlowState` of this node. */ - abstract FlowState getState(); - - /** Gets the associated configuration. */ - abstract Configuration getConfiguration(); - - /** Holds if this node is a source. */ - abstract predicate isSource(); - - abstract PathNodeImpl getASuccessorImpl(); - - private PathNodeImpl getASuccessorIfHidden() { - this.isHidden() and - result = this.getASuccessorImpl() - } - - pragma[nomagic] - private PathNodeImpl getANonHiddenSuccessor0() { - result = this.getASuccessorIfHidden*() and - not result.isHidden() - } - - final PathNodeImpl getANonHiddenSuccessor() { - result = this.getASuccessorImpl().getANonHiddenSuccessor0() and - not this.isHidden() - } - - abstract NodeEx getNodeEx(); - - predicate isHidden() { - not this.getConfiguration().includeHiddenNodes() and - ( - hiddenNode(this.getNodeEx().asNode()) and - not this.isSource() and - not this instanceof PathNodeSink - or - this.getNodeEx() instanceof TNodeImplicitRead - ) - } - - string getSourceGroup() { - this.isSource() and - this.getConfiguration().sourceGrouping(this.getNodeEx().asNode(), result) + not singleConfiguration() and + getConfig(state1).isAdditionalFlowStep(node1, node2) and + state2 = state1 } - predicate isFlowSource() { - this.isSource() and not exists(this.getSourceGroup()) - or - this instanceof PathNodeSourceGroup + predicate allowImplicitRead(Node node, ContentSet c) { + any(Configuration config).allowImplicitRead(node, c) } - predicate isFlowSink() { - this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or - this instanceof PathNodeSinkGroup - } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } - private string ppAp() { - this instanceof PathNodeSink and result = "" - or - exists(string s | s = this.(PathNodeMid).getAp().toString() | - if s = "" then result = "" else result = " " + s - ) - } + FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } - private string ppCtx() { - this instanceof PathNodeSink and result = "" - or - result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + predicate sourceGrouping(Node source, string sourceGroup) { + any(Configuration config).sourceGrouping(source, sourceGroup) } - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + predicate sinkGrouping(Node sink, string sinkGroup) { + any(Configuration config).sinkGrouping(sink, sinkGroup) } -} - -/** Holds if `n` can reach a sink. */ -private predicate directReach(PathNodeImpl n) { - n instanceof PathNodeSink or - n instanceof PathNodeSinkGroup or - directReach(n.getANonHiddenSuccessor()) -} - -/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ -private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } -/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { - n1.getANonHiddenSuccessor() = n2 and directReach(n2) + predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() } } -private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) +private import Impl as I +import I /** * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. */ -class PathNode instanceof PathNodeImpl { - PathNode() { reach(this) } - +class PathNode instanceof I::PathNode { /** Gets a textual representation of this element. */ final string toString() { result = super.toString() } @@ -3406,1548 +358,39 @@ class PathNode instanceof PathNodeImpl { } /** Gets the underlying `Node`. */ - final Node getNode() { super.getNodeEx().projectToNode() = result } + final Node getNode() { result = super.getNode() } /** Gets the `FlowState` of this node. */ - final FlowState getState() { result = super.getState() } + final FlowState getState() { result = getState(super.getState()) } /** Gets the associated configuration. */ - final Configuration getConfiguration() { result = super.getConfiguration() } + final Configuration getConfiguration() { result = getConfig(super.getState()) } /** Gets a successor of this node, if any. */ - final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } + final PathNode getASuccessor() { result = super.getASuccessor() } /** Holds if this node is a source. */ final predicate isSource() { super.isSource() } /** Holds if this node is a grouping of source nodes. */ - final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group, _) } + final predicate isSourceGroup(string group) { super.isSourceGroup(group) } /** Holds if this node is a grouping of sink nodes. */ - final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group, _) } + final predicate isSinkGroup(string group) { super.isSinkGroup(group) } } -/** - * Provides the query predicates needed to include a graph in a path-problem query. - */ -module PathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } - - /** Holds if `n` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode n, string key, string val) { - key = "semmle.label" and val = n.toString() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { - Subpaths::subpaths(arg, par, ret, out) - } -} - -/** - * An intermediate flow graph node. This is a triple consisting of a `Node`, - * a `CallContext`, and a `Configuration`. - */ -private class PathNodeMid extends PathNodeImpl, TPathNodeMid { - NodeEx node; - FlowState state; - CallContext cc; - SummaryCtx sc; - AccessPath ap; - Configuration config; - - PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - SummaryCtx getSummaryCtx() { result = sc } - - AccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - private PathNodeMid getSuccMid() { - pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx(), result.getAp()) and - result.getConfiguration() = unbindConf(this.getConfiguration()) - } - - override PathNodeImpl getASuccessorImpl() { - // an intermediate step to another intermediate node - result = this.getSuccMid() - or - // a final step to a sink - result = this.getSuccMid().projectToSink() - } - - override predicate isSource() { - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - } - - predicate isAtSink() { - sinkNode(node, state, config) and - ap instanceof AccessPathNil and - if hasSinkCallCtx(config) - then - // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` - // is exactly what we need to check. This also implies - // `sc instanceof SummaryCtxNone`. - // For `FeatureEqualSourceSinkCallContext` the initial call context was - // set to `CallContextSomeCall` and jumps are disallowed, so - // `cc instanceof CallContextNoCall` never holds. On the other hand, - // in this case there's never any need to enter a call except to identify - // a summary, so the condition in `pathIntoCallable` enforces this, which - // means that `sc instanceof SummaryCtxNone` holds if and only if we are - // in the call context of the source. - sc instanceof SummaryCtxNone or - cc instanceof CallContextNoCall - else any() - } - - PathNodeSink projectToSink() { - this.isAtSink() and - result.getNodeEx() = node and - result.getState() = state and - result.getConfiguration() = unbindConf(config) - } -} - -/** - * A flow graph node corresponding to a sink. This is disjoint from the - * intermediate nodes in order to uniquely correspond to a given sink by - * excluding the `CallContext`. - */ -private class PathNodeSink extends PathNodeImpl, TPathNodeSink { - NodeEx node; - FlowState state; - Configuration config; - - PathNodeSink() { this = TPathNodeSink(node, state, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result = TPathNodeSinkGroup(this.getSinkGroup(), config) - } - - override predicate isSource() { sourceNode(node, state, config) } - - string getSinkGroup() { config.sinkGrouping(node.asNode(), result) } -} - -private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { - string sourceGroup; - Configuration config; - - PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result.getSourceGroup() = sourceGroup and - result.getConfiguration() = config - } - - override predicate isSource() { none() } - - override string toString() { result = sourceGroup } - - override 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 - } -} - -private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { - string sinkGroup; - Configuration config; - - PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { none() } - - override predicate isSource() { none() } - - override string toString() { result = sinkGroup } - - override 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 - } -} - -private predicate pathNode( - PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, - Configuration conf, LocalCallContext localCC -) { - midnode = mid.getNodeEx() and - state = mid.getState() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - midnode.getEnclosingCallable()) and - ap = mid.getAp() -} - -/** - * Holds if data may flow from `mid` to `node`. The last step in or out of - * a callable is recorded by `cc`. - */ -pragma[assume_small_delta] -pragma[nomagic] -private predicate pathStep( - PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap -) { - exists(NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC | - pathNode(mid, midnode, state0, cc, sc, ap, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, true, _, conf, localCC) - ) - or - exists( - AccessPath ap0, NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC - | - pathNode(mid, midnode, state0, cc, sc, ap0, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), conf, - localCC) and - ap0 instanceof AccessPathNil - ) - or - jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - ap = mid.getAp() - or - additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, mid.getConfiguration()) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - pathIntoCallable(mid, node, state, _, cc, sc, _, _) and ap = mid.getAp() - or - pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone - or - pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() -} - -pragma[nomagic] -private predicate pathReadStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - tc = ap0.getHead() and - Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -pragma[nomagic] -private predicate pathStoreStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -private predicate pathOutOfCallable0( - PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa, - Configuration config -) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - apa = mid.getAp().getApprox() and - config = mid.getConfiguration() -} - -pragma[nomagic] -private predicate pathOutOfCallable1( - PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPathApprox apa, Configuration config -) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - pathOutOfCallable0(mid, pos, state, innercc, apa, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() +private predicate hasFlow(Node source, Node sink, Configuration config) { + exists(PathNode source0, PathNode sink0 | + hasFlowPath(source0, sink0, config) and + source0.getNode() = source and + sink0.getNode() = sink ) } -pragma[noinline] -private NodeEx getAnOutNodeFlow( - ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config -) { - result.asNode() = kind.getAnOutNode(call) and - Stage5::revFlow(result, _, apa, config) +private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) { + hasFlowPath(source, sink) and source.getConfiguration() = config } -/** - * Holds if data may flow from `mid` to `out`. The last step of this path - * is a return from a callable and is recorded by `cc`, if needed. - */ -pragma[noinline] -private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { - exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | - pathOutOfCallable1(mid, call, kind, state, cc, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} +private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) } -/** - * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. - */ -pragma[noinline] -private predicate pathIntoArg( - PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(ArgNodeEx arg, ArgumentPosition apos | - pathNode(mid, arg, state, cc, _, ap, config, _) and - arg.asNode().(ArgNode).argumentOf(call, apos) and - apa = ap.getApprox() and - parameterMatch(ppos, apos) - ) -} - -pragma[nomagic] -private predicate parameterCand( - DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::revFlow(p, _, apa, config) and - p.isParameterOf(callable, pos) - ) -} - -pragma[nomagic] -private predicate pathIntoCallable0( - PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, AccessPath ap, Configuration config -) { - exists(AccessPathApprox apa | - pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - callable = resolveCall(call, outercc) and - parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa), - pragma[only_bind_into](config)) - ) -} - -/** - * Holds if data may flow from `mid` to `p` through `call`. The contexts - * before and after entering the callable are `outercc` and `innercc`, - * respectively. - */ -pragma[nomagic] -private predicate pathIntoCallable( - PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, - SummaryCtx sc, DataFlowCall call, Configuration config -) { - exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | - pathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - ( - sc = TSummaryCtxSome(p, state, ap) - or - not exists(TSummaryCtxSome(p, state, ap)) and - sc = TSummaryCtxNone() and - // When the call contexts of source and sink needs to match then there's - // never any reason to enter a callable except to find a summary. See also - // the comment in `PathNodeMid::isAtSink`. - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) -} - -/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ -pragma[nomagic] -private predicate paramFlowsThrough( - ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, - AccessPathApprox apa, Configuration config -) { - exists(RetNodeEx ret | - pathNode(_, ret, state, cc, sc, ap, config, _) and - kind = ret.getKind() and - apa = ap.getApprox() and - parameterFlowThroughAllowed(sc.getParamNode(), kind) - ) -} - -pragma[nomagic] -private predicate pathThroughCallable0( - DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(CallContext innercc, SummaryCtx sc | - pathIntoCallable(mid, _, _, cc, innercc, sc, call, config) and - paramFlowsThrough(kind, state, innercc, sc, ap, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` through a callable to the node `out`. - * The context `cc` is restored to its value prior to entering the callable. - */ -pragma[noinline] -private predicate pathThroughCallable( - PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap -) { - exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config | - pathThroughCallable0(call, mid, kind, state, cc, ap, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -private module Subpaths { - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths01( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - exists(Configuration config | - pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and - pathIntoCallable(arg, par, _, _, innercc, sc, _, config) and - paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, - pragma[only_bind_into](apout), _, unbindConf(config)) and - not arg.isHidden() - ) - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `sout`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths02( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and - out.asNode() = kind.getAnOutNode(_) - } - - pragma[nomagic] - private Configuration getPathNodeConf(PathNodeImpl n) { result = n.getConfiguration() } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple. - */ - pragma[nomagic] - private predicate subpaths03( - PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, AccessPath apout - ) { - exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | - subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and - pathNode(ret, retnode, sout, innercc, sc, apout, unbindConf(getPathNodeConf(arg)), _) and - kind = retnode.getKind() - ) - } - - private PathNodeImpl localStepToHidden(PathNodeImpl n) { - n.getASuccessorImpl() = result and - result.isHidden() and - exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | - localFlowBigStep(n1, _, n2, _, _, _, _, _) or - store(n1, _, n2, _, _) or - readSet(n1, _, n2, _) - ) - } - - pragma[nomagic] - private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { - succ = pred.getANonHiddenSuccessor() and - succNode = succ.getNodeEx() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { - exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | - pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and - subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and - hasSuccessor(pragma[only_bind_into](arg), par, p) and - not ret.isHidden() and - pathNode(out0, o, sout, _, _, apout, _, _) - | - out = out0 or out = out0.projectToSink() - ) - } - - /** - * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. - */ - predicate retReach(PathNodeImpl n) { - exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) - or - exists(PathNodeImpl mid | - retReach(mid) and - n.getANonHiddenSuccessor() = mid and - not subpaths(_, mid, _, _) - ) - } -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -private predicate hasFlowPath( - PathNodeImpl flowsource, PathNodeImpl flowsink, Configuration configuration -) { - flowsource.isFlowSource() and - flowsource.getConfiguration() = configuration and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.isFlowSink() -} - -private predicate flowsTo( - PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink, - Configuration configuration -) { - flowsource.isSource() and - flowsource.getConfiguration() = configuration and - flowsource.getNodeEx().asNode() = source and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.getNodeEx().asNode() = sink -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -predicate flowsTo(Node source, Node sink, Configuration configuration) { - flowsTo(_, _, source, sink, configuration) -} - -private predicate finalStats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples -) { - fwd = true and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and - tuples = count(PathNodeImpl pn) - or - fwd = false and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and - tuples = count(PathNode pn) -} - -/** - * INTERNAL: Only for debugging. - * - * Calculates per-stage metrics for data flow. - */ -predicate stageStats( - int n, string stage, int nodes, int fields, int conscand, int states, int tuples, - Configuration config -) { - stage = "1 Fwd" and - n = 10 and - Stage1::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "1 Rev" and - n = 15 and - Stage1::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Fwd" and - n = 20 and - Stage2::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Rev" and - n = 25 and - Stage2::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Fwd" and - n = 30 and - Stage3::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Rev" and - n = 35 and - Stage3::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Fwd" and - n = 40 and - Stage4::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Rev" and - n = 45 and - Stage4::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Fwd" and - n = 50 and - Stage5::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Rev" and - n = 55 and - Stage5::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) - or - stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) -} - -private module FlowExploration { - private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - jumpStep(node1, node2, config) - or - additionalJumpStep(node1, node2, config) - or - additionalJumpStateStep(node1, _, node2, _, config) - or - // flow into callable - viableParamArgEx(_, node2, node1) - or - // flow out of a callable - viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) - | - c1 = node1.getEnclosingCallable() and - c2 = node2.getEnclosingCallable() and - c1 != c2 - ) - } - - private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSource(n) or config.isSource(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSrc(mid, config) and callableStep(mid, c, config) - ) - } - - private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSink(n) or config.isSink(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSink(mid, config) and callableStep(c, mid, config) - ) - } - - private newtype TCallableExt = - TCallable(DataFlowCallable c, Configuration config) { - interestingCallableSrc(c, config) or - interestingCallableSink(c, config) - } or - TCallableSrc() or - TCallableSink() - - private predicate callableExtSrc(TCallableSrc src) { any() } - - private predicate callableExtSink(TCallableSink sink) { any() } - - private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { - exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | - callableStep(c1, c2, config) and - ce1 = TCallable(c1, pragma[only_bind_into](config)) and - ce2 = TCallable(c2, pragma[only_bind_into](config)) - ) - or - exists(Node n, Configuration config | - ce1 = TCallableSrc() and - (config.isSource(n) or config.isSource(n, _)) and - ce2 = TCallable(getNodeEnclosingCallable(n), config) - ) - or - exists(Node n, Configuration config | - ce2 = TCallableSink() and - (config.isSink(n) or config.isSink(n, _)) and - ce1 = TCallable(getNodeEnclosingCallable(n), config) - ) - } - - private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { - callableExtStepFwd(ce2, ce1) - } - - private int distSrcExt(TCallableExt c) = - shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) - - private int distSinkExt(TCallableExt c) = - shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) - - private int distSrc(DataFlowCallable c, Configuration config) { - result = distSrcExt(TCallable(c, config)) - 1 - } - - private int distSink(DataFlowCallable c, Configuration config) { - result = distSinkExt(TCallable(c, config)) - 1 - } - - private newtype TPartialAccessPath = - TPartialNil(DataFlowType t) or - TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first - * element of the list and its length are tracked. If data flows from a source to - * a given node with a given `AccessPath`, this indicates the sequence of - * dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ - private class PartialAccessPath extends TPartialAccessPath { - abstract string toString(); - - TypedContent getHead() { this = TPartialCons(result, _) } - - int len() { - this = TPartialNil(_) and result = 0 - or - this = TPartialCons(_, result) - } - - DataFlowType getType() { - this = TPartialNil(result) - or - exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) - } - } - - private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { - override string toString() { - exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) - } - } - - private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { - override string toString() { - exists(TypedContent tc, int len | this = TPartialCons(tc, len) | - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private newtype TRevPartialAccessPath = - TRevPartialNil() or - TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `Content`s, but only the first - * element of the list and its length are tracked. - */ - private class RevPartialAccessPath extends TRevPartialAccessPath { - abstract string toString(); - - Content getHead() { this = TRevPartialCons(result, _) } - - int len() { - this = TRevPartialNil() and result = 0 - or - this = TRevPartialCons(_, result) - } - } - - private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { - override string toString() { result = "" } - } - - private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { - override string toString() { - exists(Content c, int len | this = TRevPartialCons(c, len) | - if len = 1 - then result = "[" + c.toString() + "]" - else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private predicate relevantState(FlowState state) { - sourceNode(_, state, _) or - sinkNode(_, state, _) or - additionalLocalStateStep(_, state, _, _, _) or - additionalLocalStateStep(_, _, _, state, _) or - additionalJumpStateStep(_, state, _, _, _) or - additionalJumpStateStep(_, _, _, state, _) - } - - private newtype TSummaryCtx1 = - TSummaryCtx1None() or - TSummaryCtx1Param(ParamNodeEx p) - - private newtype TSummaryCtx2 = - TSummaryCtx2None() or - TSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TSummaryCtx3 = - TSummaryCtx3None() or - TSummaryCtx3Some(PartialAccessPath ap) - - private newtype TRevSummaryCtx1 = - TRevSummaryCtx1None() or - TRevSummaryCtx1Some(ReturnPosition pos) - - private newtype TRevSummaryCtx2 = - TRevSummaryCtx2None() or - TRevSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TRevSummaryCtx3 = - TRevSummaryCtx3None() or - TRevSummaryCtx3Some(RevPartialAccessPath ap) - - private newtype TPartialPathNode = - TPartialPathNodeFwd( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = TPartialNil(node.getDataFlowType()) and - exists(config.explorationLimit()) - or - partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap, config) and - distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() - } or - TPartialPathNodeRev( - NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, - RevPartialAccessPath ap, Configuration config - ) { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() and - exists(config.explorationLimit()) - or - revPartialPathStep(_, node, state, sc1, sc2, sc3, ap, config) and - not clearsContentEx(node, ap.getHead()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead()) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() - } - - pragma[nomagic] - private predicate partialPathNodeMk0( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap, config) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - not clearsContentEx(node, ap.getHead().getContent()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead().getContent()) - ) and - if node.asNode() instanceof CastingNode - then compatibleTypes(node.getDataFlowType(), ap.getType()) - else any() - } - - /** - * A `Node` augmented with a call context, an access path, and a configuration. - */ - class PartialPathNode extends TPartialPathNode { - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() - } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - /** Gets the underlying `Node`. */ - final Node getNode() { this.getNodeEx().projectToNode() = result } - - FlowState getState() { none() } - - private NodeEx getNodeEx() { - result = this.(PartialPathNodeFwd).getNodeEx() or - result = this.(PartialPathNodeRev).getNodeEx() - } - - /** Gets the associated configuration. */ - Configuration getConfiguration() { none() } - - /** Gets a successor of this node, if any. */ - PartialPathNode getASuccessor() { none() } - - /** - * Gets the approximate distance to the nearest source measured in number - * of interprocedural steps. - */ - int getSourceDistance() { - result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - /** - * Gets the approximate distance to the nearest sink measured in number - * of interprocedural steps. - */ - int getSinkDistance() { - result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - private string ppAp() { - exists(string s | - s = this.(PartialPathNodeFwd).getAp().toString() or - s = this.(PartialPathNodeRev).getAp().toString() - | - if s = "" then result = "" else result = " " + s - ) - } - - private string ppCtx() { - result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" - } - - /** Holds if this is a source in a forward-flow path. */ - predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } - - /** Holds if this is a sink in a reverse-flow path. */ - predicate isRevSink() { this.(PartialPathNodeRev).isSink() } - } - - /** - * Provides the query predicates needed to include a graph in a path-problem query. - */ - module PartialPathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } - } - - private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { - NodeEx node; - FlowState state; - CallContext cc; - TSummaryCtx1 sc1; - TSummaryCtx2 sc2; - TSummaryCtx3 sc3; - PartialAccessPath ap; - Configuration config; - - PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - TSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TSummaryCtx3 getSummaryCtx3() { result = sc3 } - - PartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeFwd getASuccessor() { - partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp(), - result.getConfiguration()) - } - - predicate isSource() { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap instanceof TPartialNil - } - } - - private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { - NodeEx node; - FlowState state; - TRevSummaryCtx1 sc1; - TRevSummaryCtx2 sc2; - TRevSummaryCtx3 sc3; - RevPartialAccessPath ap; - Configuration config; - - PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } - - RevPartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeRev getASuccessor() { - revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), - this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp(), this.getConfiguration()) - } - - predicate isSink() { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() - } - } - - private predicate partialPathStep( - PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, - TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and - ( - localFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - ) - or - jumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - partialPathStoreStep(mid, _, _, node, ap) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(PartialAccessPath ap0, TypedContent tc | - partialPathReadStep(mid, ap0, tc, node, cc, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsFwd(ap, tc, ap0, config) - ) - or - partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap, config) - or - partialPathOutOfCallable(mid, node, state, cc, ap, config) and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() - or - partialPathThroughCallable(mid, node, state, cc, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - bindingset[result, i] - private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } - - pragma[inline] - private predicate partialPathStoreStep( - PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, - PartialAccessPath ap2 - ) { - exists(NodeEx midNode, DataFlowType contentType | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - store(midNode, tc, node, contentType, mid.getConfiguration()) and - ap2.getHead() = tc and - ap2.len() = unbindInt(ap1.len() + 1) and - compatibleTypes(ap1.getType(), contentType) - ) - } - - pragma[nomagic] - private predicate apConsFwd( - PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeFwd mid | - partialPathStoreStep(mid, ap1, tc, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate partialPathReadStep( - PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc, - Configuration config - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and - ap.getHead() = tc and - pragma[only_bind_into](config) = mid.getConfiguration() and - cc = mid.getCallContext() - ) - } - - private predicate partialPathOutOfCallable0( - PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, - PartialAccessPath ap, Configuration config - ) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - ap = mid.getAp() and - config = mid.getConfiguration() - } - - pragma[nomagic] - private predicate partialPathOutOfCallable1( - PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - partialPathOutOfCallable0(mid, pos, state, innercc, ap, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) - } - - private predicate partialPathOutOfCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(ReturnKindExt kind, DataFlowCall call | - partialPathOutOfCallable1(mid, call, kind, state, cc, ap, config) - | - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[noinline] - private predicate partialPathIntoArg( - PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ArgNode arg, ArgumentPosition apos | - arg = mid.getNodeEx().asNode() and - state = mid.getState() and - cc = mid.getCallContext() and - arg.argumentOf(call, apos) and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate partialPathIntoCallable0( - PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - partialPathIntoArg(mid, pos, state, outercc, call, ap, config) and - callable = resolveCall(call, outercc) - } - - private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, - CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ParameterPosition pos, DataFlowCallable callable | - partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - sc1 = TSummaryCtx1Param(p) and - sc2 = TSummaryCtx2Some(state) and - sc3 = TSummaryCtx3Some(ap) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) - } - - pragma[nomagic] - private predicate paramFlowsThroughInPartialPath( - ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeFwd mid, RetNodeEx ret | - mid.getNodeEx() = ret and - kind = ret.getKind() and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() and - ap = mid.getAp() - ) - } - - pragma[noinline] - private predicate partialPathThroughCallable0( - DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | - partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _, config) and - paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap, config) - ) - } - - private predicate partialPathThroughCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ReturnKindExt kind | - partialPathThroughCallable0(call, mid, kind, state, cc, ap, config) and - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[nomagic] - private predicate revPartialPathStep( - PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, - TRevSummaryCtx3 sc3, RevPartialAccessPath ap, Configuration config - ) { - localFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - jumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - revPartialPathReadStep(mid, _, _, node, ap) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(RevPartialAccessPath ap0, Content c | - revPartialPathStoreStep(mid, ap0, c, node, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsRev(ap, c, ap0, config) - ) - or - exists(ParamNodeEx p | - mid.getNodeEx() = p and - viableParamArgEx(_, p, node) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - or - exists(ReturnPosition pos | - revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap, config) and - pos = getReturnPosition(node.asNode()) - ) - or - revPartialPathThroughCallable(mid, node, state, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - pragma[inline] - private predicate revPartialPathReadStep( - PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, - RevPartialAccessPath ap2 - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - read(node, c, midNode, mid.getConfiguration()) and - ap2.getHead() = c and - ap2.len() = unbindInt(ap1.len() + 1) - ) - } - - pragma[nomagic] - private predicate apConsRev( - RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeRev mid | - revPartialPathReadStep(mid, ap1, c, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathStoreStep( - PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config - ) { - exists(NodeEx midNode, TypedContent tc | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - store(node, tc, midNode, _, config) and - ap.getHead() = c and - config = mid.getConfiguration() and - tc.getContent() = c - ) - } - - pragma[nomagic] - private predicate revPartialPathIntoReturn( - PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, - TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap, - Configuration config - ) { - exists(NodeEx out | - mid.getNodeEx() = out and - mid.getState() = state and - viableReturnPosOutEx(call, pos, out) and - sc1 = TRevSummaryCtx1Some(pos) and - sc2 = TRevSummaryCtx2Some(state) and - sc3 = TRevSummaryCtx3Some(ap) and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathFlowsThrough( - ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, - TRevSummaryCtx3Some sc3, RevPartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | - mid.getNodeEx() = p and - mid.getState() = state and - p.getPosition() = ppos and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable0( - DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, - RevPartialAccessPath ap, Configuration config - ) { - exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | - revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _, config) and - revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap, config) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ArgumentPosition pos | - revPartialPathThroughCallable0(call, mid, pos, state, ap, config) and - node.asNode().(ArgNode).argumentOf(call, pos) - ) - } -} - -import FlowExploration - -private predicate partialFlow( - PartialPathNode source, PartialPathNode node, Configuration configuration -) { - source.getConfiguration() = configuration and - source.isFwdSource() and - node = source.getASuccessor+() -} - -private predicate revPartialFlow( - PartialPathNode node, PartialPathNode sink, Configuration configuration -) { - sink.getConfiguration() = configuration and - sink.isRevSink() and - node.getASuccessor+() = sink -} +predicate flowsTo = hasFlow/3; diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl3.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl3.qll index 1b969756b09..e6bdc74cceb 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl3.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl3.qll @@ -1,16 +1,16 @@ /** - * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from - * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed - * through the `Configuration` class. This file exists in several identical - * copies, allowing queries to use multiple `Configuration` classes that depend - * on each other without introducing mutual recursion among those configurations. + * DEPRECATED: Use `Make` and `MakeWithState` instead. + * + * Provides a `Configuration` class backwards-compatible interface to the data + * flow library. */ private import DataFlowImplCommon private import DataFlowImplSpecific::Private import DataFlowImplSpecific::Public +private import DataFlowImpl import DataFlowImplCommonPublic +import FlowStateString /** * A configuration of interprocedural data flow analysis. This defines @@ -144,6 +144,10 @@ abstract class Configuration extends string { * - `FeatureEqualSourceSinkCallContext`: * Implies both of the above and additionally ensures that the entire flow * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. */ FlowFeature getAFeature() { none() } @@ -156,7 +160,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from `source` to `sink` for this configuration. */ - predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } + predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) } /** * Holds if data may flow from `source` to `sink` for this configuration. @@ -169,9 +173,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from some source to `sink` for this configuration. */ - predicate hasFlowTo(Node sink) { - sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode() - } + predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) } /** * Holds if data may flow from some source to `sink` for this configuration. @@ -179,10 +181,12 @@ abstract class Configuration extends string { predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } /** + * DEPRECATED: Use `FlowExploration` instead. + * * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` * measured in approximate number of interprocedural steps. */ - int explorationLimit() { none() } + deprecated int explorationLimit() { none() } /** * Holds if hidden nodes should be included in the data flow graph. @@ -191,49 +195,6 @@ abstract class Configuration extends string { * is not visualized (for example in a `path-problem` query). */ predicate includeHiddenNodes() { none() } - - /** - * Holds if there is a partial data flow path from `source` to `node`. The - * approximate distance between `node` and the closest source is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards sink definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sources is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - */ - final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { - partialFlow(source, node, this) and - dist = node.getSourceDistance() - } - - /** - * Holds if there is a partial data flow path from `node` to `sink`. The - * approximate distance between `node` and the closest sink is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards source definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sinks is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - * - * Note that reverse flow has slightly lower precision than the corresponding - * forward flow, as reverse flow disregards type pruning among other features. - */ - final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { - revPartialFlow(node, sink, this) and - dist = node.getSinkDistance() - } } /** @@ -263,90 +224,6 @@ abstract private class ConfigurationRecursionPrevention extends Configuration { } } -private newtype TNodeEx = - TNodeNormal(Node n) or - TNodeImplicitRead(Node n, boolean hasRead) { - any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true] - } - -private class NodeEx extends TNodeEx { - string toString() { - result = this.asNode().toString() - or - exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") - } - - Node asNode() { this = TNodeNormal(result) } - - predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } - - Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } - - pragma[nomagic] - private DataFlowCallable getEnclosingCallable0() { - nodeEnclosingCallable(this.projectToNode(), result) - } - - pragma[inline] - DataFlowCallable getEnclosingCallable() { - pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) - } - - pragma[nomagic] - private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } - - pragma[inline] - DataFlowType getDataFlowType() { - pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) - } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -private class ArgNodeEx extends NodeEx { - ArgNodeEx() { this.asNode() instanceof ArgNode } -} - -private class ParamNodeEx extends NodeEx { - ParamNodeEx() { this.asNode() instanceof ParamNode } - - predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { - this.asNode().(ParamNode).isParameterOf(c, pos) - } - - ParameterPosition getPosition() { this.isParameterOf(_, result) } -} - -private class RetNodeEx extends NodeEx { - RetNodeEx() { this.asNode() instanceof ReturnNodeExt } - - ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } - - ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } -} - -private predicate inBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierIn(n) - | - config.isSource(n) or config.isSource(n, _) - ) -} - -private predicate outBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierOut(n) - | - config.isSink(n) or config.isSink(n, _) - ) -} - /** A bridge class to access the deprecated `isBarrierGuard`. */ private class BarrierGuardGuardedNodeBridge extends Unit { abstract predicate guardedNode(Node n, Configuration config); @@ -370,3019 +247,94 @@ private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { } } -pragma[nomagic] -private predicate fullBarrier(NodeEx node, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n) +private FlowState relevantState(Configuration config) { + config.isSource(_, result) or + config.isSink(_, result) or + config.isBarrier(_, result) or + config.isAdditionalFlowStep(_, result, _, _) or + config.isAdditionalFlowStep(_, _, _, result) +} + +private newtype TConfigState = + TMkConfigState(Configuration config, FlowState state) { + state = relevantState(config) or state instanceof FlowStateEmpty + } + +private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) } + +private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) } + +private predicate singleConfiguration() { 1 = strictcount(Configuration c) } + +private module Config implements FullStateConfigSig { + class FlowState = TConfigState; + + predicate isSource(Node source, FlowState state) { + getConfig(state).isSource(source, getState(state)) or - config.isBarrierIn(n) and - not config.isSource(n) and - not config.isSource(n, _) + getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty + } + + predicate isSink(Node sink, FlowState state) { + getConfig(state).isSink(sink, getState(state)) or - config.isBarrierOut(n) and - not config.isSink(n) and - not config.isSink(n, _) + getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty + } + + predicate isBarrier(Node node) { none() } + + predicate isBarrier(Node node, FlowState state) { + getConfig(state).isBarrier(node, getState(state)) or + getConfig(state).isBarrier(node) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state)) + } + + predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) } + + predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) } + + predicate isAdditionalFlowStep(Node node1, Node node2) { + singleConfiguration() and + any(Configuration config).isAdditionalFlowStep(node1, node2) + } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and + getConfig(state2) = getConfig(state1) or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config) - ) -} - -pragma[nomagic] -private predicate stateBarrier(NodeEx node, FlowState state, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n, state) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config) - ) -} - -pragma[nomagic] -private predicate sourceNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSource(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSource(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -pragma[nomagic] -private predicate sinkNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSink(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSink(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -/** Provides the relevant barriers for a step from `node1` to `node2`. */ -pragma[inline] -private predicate stepFilter(NodeEx node1, NodeEx node2, Configuration config) { - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) -} - -/** - * Holds if data can flow in one local step from `node1` to `node2`. - */ -private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.asNode() = n and - node2.isImplicitReadNode(n, false) and - not fullBarrier(node1, config) - ) -} - -/** - * Holds if the additional step from `node1` to `node2` does not jump between callables. - */ -private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.isImplicitReadNode(n, true) and - node2.asNode() = n and - not fullBarrier(node2, config) - ) -} - -private predicate additionalLocalStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) - ) -} - -/** - * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. - */ -private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if the additional step from `node1` to `node2` jumps between callables. - */ -private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate additionalJumpStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -pragma[nomagic] -private predicate readSet(NodeEx node1, ContentSet c, NodeEx node2, Configuration config) { - readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and - stepFilter(node1, node2, config) - or - exists(Node n | - node2.isImplicitReadNode(n, true) and - node1.isImplicitReadNode(n, _) and - config.allowImplicitRead(n, c) - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(ContentSet cs | - readSet(node1, cs, node2, config) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate clearsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - clearsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate expectsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - expectsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -pragma[nomagic] -private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } - -pragma[nomagic] -private predicate hasReadStep(Content c, Configuration config) { read(_, c, _, config) } - -pragma[nomagic] -private predicate store( - NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config -) { - store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), - contentType) and - hasReadStep(tc.getContent(), config) and - stepFilter(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { - viableReturnPosOut(call, pos, out.asNode()) -} - -pragma[nomagic] -private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { - viableParamArg(call, p.asNode(), arg.asNode()) -} - -/** - * Holds if field flow should be used for the given configuration. - */ -private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } - -private predicate hasSourceCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSourceCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate hasSinkCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSinkCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if flow from `p` to a return node of kind `kind` is allowed. - * - * We don't expect a parameter to return stored in itself, unless - * explicitly allowed - */ -bindingset[p, kind] -private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { - exists(ParameterPosition pos | p.isParameterOf(_, pos) | - not kind.(ParamUpdateReturnKind).getPosition() = pos - or - allowParameterReturnInSelfCached(p.asNode()) - ) -} - -private module Stage1 implements StageSig { - class Ap extends int { - // workaround for bad functionality-induced joins (happens when using `Unit`) - pragma[nomagic] - Ap() { this in [0 .. 1] and this < 1 } - } - - private class Cc = boolean; - - /* Begin: Stage 1 logic. */ - /** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `cc` records whether the node is reached through an - * argument in a call. - */ - private predicate fwdFlow(NodeEx node, Cc cc, Configuration config) { - sourceNode(node, _, config) and - if hasSourceCallCtx(config) then cc = true else cc = false - or - exists(NodeEx mid | fwdFlow(mid, cc, config) | - localFlowStep(mid, node, config) or - additionalLocalFlowStep(mid, node, config) or - additionalLocalStateStep(mid, _, node, _, config) - ) - or - exists(NodeEx mid | fwdFlow(mid, _, config) and cc = false | - jumpStep(mid, node, config) or - additionalJumpStep(mid, node, config) or - additionalJumpStateStep(mid, _, node, _, config) - ) - or - // store - exists(NodeEx mid | - useFieldFlow(config) and - fwdFlow(mid, cc, config) and - store(mid, _, node, _, config) - ) - or - // read - exists(ContentSet c | - fwdFlowReadSet(c, node, cc, config) and - fwdFlowConsCandSet(c, _, config) - ) - or - // flow into a callable - fwdFlowIn(_, _, _, node, config) and - cc = true - or - // flow out of a callable - fwdFlowOut(_, node, false, config) and - cc = false - or - // flow through a callable - exists(DataFlowCall call | - fwdFlowOutFromArg(call, node, config) and - fwdFlowIsEntered(call, cc, config) - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowIn( - DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p, Configuration config - ) { - // call context cannot help reduce virtual dispatch - fwdFlow(arg, cc, config) and - viableParamArgEx(call, p, arg) and - not fullBarrier(p, config) and - ( - cc = false - or - cc = true and - not reducedViableImplInCallContext(call, _, _) - ) - or - // call context may help reduce virtual dispatch - exists(DataFlowCallable target | - fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target, config) and - target = viableImplInSomeFwdFlowCallContextExt(call, config) and - cc = true - ) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - fwdFlowIn(call, _, cc, _, config) - } - - pragma[nomagic] - private predicate fwdFlowInReducedViableImplInSomeCallContext( - DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target, Configuration config - ) { - fwdFlow(arg, true, config) and - viableParamArgEx(call, p, arg) and - reducedViableImplInCallContext(call, _, _) and - target = p.getEnclosingCallable() and - not fullBarrier(p, config) - } - - /** - * Gets a viable dispatch target of `call` in the context `ctx`. This is - * restricted to those `call`s for which a context might make a difference, - * and to `ctx`s that are reachable in `fwdFlow`. - */ - pragma[nomagic] - private DataFlowCallable viableImplInSomeFwdFlowCallContextExt( - DataFlowCall call, Configuration config - ) { - exists(DataFlowCall ctx | - fwdFlowIsEntered(ctx, _, config) and - result = viableImplInCallContextExt(call, ctx) - ) - } - - private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) } - - pragma[nomagic] - private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc, Configuration config) { - exists(NodeEx mid | - fwdFlow(mid, cc, config) and - readSet(mid, c, node, config) - ) - } - - /** - * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - fwdFlow(mid, _, config) and - store(mid, tc, node, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `cs` may be interpreted in a read as the target of some store - * into `c`, in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCandSet(ContentSet cs, Content c, Configuration config) { - fwdFlowConsCand(c, config) and - c = cs.getAReadContent() - } - - pragma[nomagic] - private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { - exists(RetNodeEx ret | - fwdFlow(ret, cc, config) and - ret.getReturnPosition() = pos - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) { - exists(ReturnPosition pos | - fwdFlowReturnPosition(pos, cc, config) and - viableReturnPosOutEx(call, pos, out) and - not fullBarrier(out, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) { - fwdFlowOut(call, out, true, config) - } - - private predicate stateStepFwd(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1 | - additionalLocalStateStep(node1, state1, _, state2, config) or - additionalJumpStateStep(node1, state1, _, state2, config) - | - fwdFlow(node1, config) - ) - } - - private predicate fwdFlowState(FlowState state, Configuration config) { - sourceNode(_, state, config) - or - exists(FlowState state0 | - fwdFlowState(state0, config) and - stateStepFwd(state0, state, config) - ) - } - - /** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink. - */ - pragma[nomagic] - private predicate revFlow(NodeEx node, boolean toReturn, Configuration config) { - revFlow0(node, toReturn, config) and - fwdFlow(node, config) - } - - pragma[nomagic] - private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) { - exists(FlowState state | - fwdFlow(node, pragma[only_bind_into](config)) and - sinkNode(node, state, config) and - fwdFlowState(state, pragma[only_bind_into](config)) and - if hasSinkCallCtx(config) then toReturn = true else toReturn = false - ) - or - exists(NodeEx mid | revFlow(mid, toReturn, config) | - localFlowStep(node, mid, config) or - additionalLocalFlowStep(node, mid, config) or - additionalLocalStateStep(node, _, mid, _, config) - ) - or - exists(NodeEx mid | revFlow(mid, _, config) and toReturn = false | - jumpStep(node, mid, config) or - additionalJumpStep(node, mid, config) or - additionalJumpStateStep(node, _, mid, _, config) - ) - or - // store - exists(Content c | - revFlowStore(c, node, toReturn, config) and - revFlowConsCand(c, config) - ) - or - // read - exists(NodeEx mid, ContentSet c | - readSet(node, c, mid, config) and - fwdFlowConsCandSet(c, _, pragma[only_bind_into](config)) and - revFlow(mid, toReturn, pragma[only_bind_into](config)) - ) - or - // flow into a callable - revFlowIn(_, node, false, config) and - toReturn = false - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(pos, config) and - node.(RetNodeEx).getReturnPosition() = pos and - toReturn = true - ) - or - // flow through a callable - exists(DataFlowCall call | - revFlowInToReturn(call, node, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - /** - * Holds if `c` is the target of a read in the flow covered by `revFlow`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, ContentSet cs | - fwdFlow(node, pragma[only_bind_into](config)) and - readSet(node, cs, mid, config) and - fwdFlowConsCandSet(cs, c, pragma[only_bind_into](config)) and - revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) { - exists(NodeEx mid, TypedContent tc | - revFlow(mid, toReturn, pragma[only_bind_into](config)) and - fwdFlowConsCand(c, pragma[only_bind_into](config)) and - store(node, tc, mid, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `c` is the target of both a read and a store in the flow covered - * by `revFlow`. - */ - pragma[nomagic] - additional predicate revFlowIsReadAndStored(Content c, Configuration conf) { - revFlowConsCand(c, conf) and - revFlowStore(c, _, _, conf) - } - - pragma[nomagic] - additional predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config - ) { - fwdFlowReturnPosition(pos, _, config) and - viableReturnPosOutEx(call, pos, out) - } - - pragma[nomagic] - private predicate revFlowOut(ReturnPosition pos, Configuration config) { - exists(NodeEx out | - revFlow(out, _, config) and - viableReturnPosOutNodeCandFwd1(_, pos, out, config) - ) - } - - pragma[nomagic] - additional predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config - ) { - fwdFlowIn(call, arg, _, p, config) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate revFlowIn( - DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config - ) { - exists(ParamNodeEx p | - revFlow(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) { - revFlowIn(call, arg, true, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(NodeEx out | - revFlow(out, toReturn, config) and - fwdFlowOutFromArg(call, out, config) - ) - } - - private predicate stateStepRev(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - additionalLocalStateStep(node1, state1, node2, state2, config) or - additionalJumpStateStep(node1, state1, node2, state2, config) - | - revFlow(node1, _, pragma[only_bind_into](config)) and - revFlow(node2, _, pragma[only_bind_into](config)) and - fwdFlowState(state1, pragma[only_bind_into](config)) and - fwdFlowState(state2, pragma[only_bind_into](config)) - ) - } - - additional predicate revFlowState(FlowState state, Configuration config) { - exists(NodeEx node | - sinkNode(node, state, config) and - revFlow(node, _, pragma[only_bind_into](config)) and - fwdFlowState(state, pragma[only_bind_into](config)) - ) - or - exists(FlowState state0 | - revFlowState(state0, config) and - stateStepRev(state, state0, config) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Content c | - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - revFlow(node2, pragma[only_bind_into](config)) and - store(node1, tc, node2, contentType, config) and - c = tc.getContent() and - exists(ap1) - ) - } - - pragma[nomagic] - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) { - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - read(n1, c, n2, pragma[only_bind_into](config)) and - revFlow(n2, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, config) and - exists(ap) - } - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, _, pragma[only_bind_into](config)) and - exists(state) and - exists(ap) - } - - private predicate throughFlowNodeCand(NodeEx node, Configuration config) { - revFlow(node, true, config) and - fwdFlow(node, true, config) and - not inBarrier(node, config) and - not outBarrier(node, config) - } - - /** Holds if flow may return from `callable`. */ - pragma[nomagic] - private predicate returnFlowCallableNodeCand( - DataFlowCallable callable, ReturnKindExt kind, Configuration config - ) { - exists(RetNodeEx ret | - throughFlowNodeCand(ret, config) and - callable = ret.getEnclosingCallable() and - kind = ret.getKind() - ) - } - - /** - * Holds if flow may enter through `p` and reach a return node making `p` a - * candidate for the origin of a summary. - */ - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(DataFlowCallable c, ReturnKindExt kind | - throughFlowNodeCand(p, config) and - returnFlowCallableNodeCand(c, kind, config) and - p.getEnclosingCallable() = c and - exists(ap) and - parameterFlowThroughAllowed(p, kind) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - throughFlowNodeCand(ret, config) and - kind = ret.getKind() and - exists(argAp) and - exists(ap) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, boolean toReturn | - revFlow(arg, toReturn, config) and - revFlowInToReturn(call, arg, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, config)) and - fields = count(Content f0 | fwdFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | fwdFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, config)) and - fields = count(Content f0 | revFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | revFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | revFlow(n, b, config)) - } - /* End: Stage 1 logic. */ -} - -pragma[noinline] -private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - localFlowStep(node1, node2, config) -} - -pragma[noinline] -private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - additionalLocalFlowStep(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCand1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config -) { - Stage1::revFlow(out, config) and - Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, Configuration config -) { - exists(ReturnPosition pos | - viableReturnPosOutNodeCand1(call, pos, out, config) and - pos = ret.getReturnPosition() and - kind = pos.getKind() and - Stage1::revFlow(ret, config) and - not outBarrier(ret, config) and - not inBarrier(out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCand1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config -) { - Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and - Stage1::revFlow(arg, config) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config -) { - viableParamArgNodeCand1(call, p, arg, config) and - Stage1::revFlow(p, config) and - not outBarrier(arg, config) and - not inBarrier(p, config) -} - -/** - * Gets the amount of forward branching on the origin of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int branch(NodeEx n1, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n1, _, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) - ) -} - -/** - * Gets the amount of backward branching on the target of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int join(NodeEx n2, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n, _, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) - ) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. The - * `allowsFieldFlow` flag indicates whether the branching is within the limit - * specified by the configuration. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, ret, kind, out, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(ret, pragma[only_bind_into](config)) and - j = join(out, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. The `allowsFieldFlow` flag indicates whether - * the branching is within the limit specified by the configuration. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config -) { - flowIntoCallNodeCand1(call, arg, p, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(arg, pragma[only_bind_into](config)) and - j = join(p, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -private signature module StageSig { - class Ap; - - predicate revFlow(NodeEx node, Configuration config); - - predicate revFlowAp(NodeEx node, Ap ap, Configuration config); - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config); - - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config); - - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config); - - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ); - - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ); - - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config); -} - -private module MkStage { - class ApApprox = PrevStage::Ap; - - signature module StageParam { - class Ap; - - class ApNil extends Ap; - - bindingset[result, ap] - ApApprox getApprox(Ap ap); - - ApNil getApNil(NodeEx node); - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail); - - /** - * An approximation of `Content` that corresponds to the precision level of - * `Ap`, such that the mappings from both `Ap` and `Content` to this type - * are functional. - */ - class ApHeadContent; - - ApHeadContent getHeadContent(Ap ap); - - ApHeadContent projectToHeadContent(Content c); - - class ApOption; - - ApOption apNone(); - - ApOption apSome(Ap ap); - - class Cc; - - class CcCall extends Cc; - - // TODO: member predicate on CcCall - predicate matchesCall(CcCall cc, DataFlowCall call); - - class CcNoCall extends Cc; - - Cc ccNone(); - - CcCall ccSomeCall(); - - class LocalCc; - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc); - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ); - - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config - ); - - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config - ); - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config); - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType); - } - - module Stage implements StageSig { - import Param - - /* Begin: Stage logic. */ - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - private predicate revFlowApAlias(NodeEx node, ApApprox apa, Configuration config) { - PrevStage::revFlowAp(node, apa, config) - } - - pragma[nomagic] - private predicate flowIntoCallApa( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa, - Configuration config - ) { - flowIntoCall(call, arg, p, allowsFieldFlow, config) and - PrevStage::revFlowAp(p, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(arg, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowOutOfCallApa( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - ApApprox apa, Configuration config - ) { - flowOutOfCall(call, ret, kind, out, allowsFieldFlow, config) and - PrevStage::revFlowAp(out, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(ret, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowThroughOutOfCall( - DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, - ApApprox argApa, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa, pragma[only_bind_into](config)) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) and - matchesCall(ccc, call) - ) - } - - /** - * Holds if `node` is reachable with access path `ap` from a source in the - * configuration `config`. - * - * The call context `cc` records whether the node is reached through an - * argument in a call, and if so, `summaryCtx` and `argAp` record the - * corresponding parameter position and access path of that argument, respectively. - */ - pragma[nomagic] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa, config) and - PrevStage::revFlow(node, state, apa, config) and - filter(node, state, ap, config) - } - - pragma[inline] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, cc, summaryCtx, argAp, ap, _, config) - } - - pragma[nomagic] - private predicate fwdFlow0( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - sourceNode(node, state, config) and - (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and - argAp = apNone() and - summaryCtx = TParamNodeNone() and - ap = getApNil(node) and - apa = getApprox(ap) - or - exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | - fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0, config) and - localCc = getLocalCc(mid, cc) - | - localStep(mid, state0, node, state, true, _, config, localCc) and - ap = ap0 and - apa = apa0 - or - localStep(mid, state0, node, state, false, ap, config, localCc) and - ap0 instanceof ApNil and - apa = getApprox(ap) - ) - or - exists(NodeEx mid | - fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa, pragma[only_bind_into](config)) and - jumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(mid, state, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(mid, state0, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStateStep(mid, state0, node, state, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - // store - exists(TypedContent tc, Ap ap0 | - fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp, config) and - ap = apCons(tc, ap0) and - apa = getApprox(ap) - ) - or - // read - exists(Ap ap0, Content c | - fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp, config) and - fwdFlowConsCand(ap0, c, ap, config) and - apa = getApprox(ap) - ) - or - // flow into a callable - fwdFlowIn(_, node, state, _, cc, _, _, ap, apa, config) and - if PrevStage::parameterMayFlowThrough(node, apa, config) - then ( - summaryCtx = TParamNodeSome(node.asNode()) and - argAp = apSome(ap) - ) else ( - summaryCtx = TParamNodeNone() and argAp = apNone() - ) - or - // flow out of a callable - exists( - DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, - DataFlowCallable inner - | - fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa, config) and - flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa, config) and - inner = ret.getEnclosingCallable() and - cc = getCallContextReturn(inner, call, innercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - or - // flow through a callable - exists( - DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, ApApprox innerArgApa - | - fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowStore( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(DataFlowType contentType, ApApprox apa1 | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1, config) and - PrevStage::storeStepCand(node1, apa1, tc, node2, contentType, config) and - typecheckStore(ap1, contentType) - ) - } - - /** - * Holds if forward flow with access path `tail` reaches a store of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(TypedContent tc | - fwdFlowStore(_, tail, tc, _, _, _, _, _, config) and - tc.getContent() = c and - cons = apCons(tc, tail) - ) - } - - pragma[nomagic] - private predicate readStepCand( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - PrevStage::readStepCand(node1, c, node2, config) and - apc = projectToHeadContent(c) - } - - bindingset[node1, apc] - pragma[inline_late] - private predicate readStepCand0( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - readStepCand(node1, apc, c, node2, config) - } - - pragma[nomagic] - private predicate fwdFlowRead( - Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(ApHeadContent apc | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap, config) and - apc = getHeadContent(ap) and - readStepCand0(node1, apc, c, node2, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowIn( - DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, - ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa, Configuration config - ) { - exists(ArgNodeEx arg, boolean allowsFieldFlow | - fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa, config) and - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowRetFromArg( - RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, ApApprox argApa, - Ap ap, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - fwdFlow(pragma[only_bind_into](ret), state, ccc, - TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), - pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa), - pragma[only_bind_into](config)) and - kind = ret.getKind() and - parameterFlowThroughAllowed(summaryCtx, kind) and - argApa = getApprox(argAp) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) - ) - } - - pragma[inline] - private predicate fwdFlowThrough0( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, - Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa, config) and - fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp, config) - } - - pragma[nomagic] - private predicate fwdFlowThrough( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa, - config) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` - * and data might flow through the target callable and back out at `call`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered( - DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, - ParamNodeEx p, Ap ap, Configuration config - ) { - exists(ApApprox apa | - fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(p, apa, config) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate storeStepFwd( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config - ) { - fwdFlowStore(node1, ap1, tc, node2, _, _, _, _, config) and - ap2 = apCons(tc, ap1) and - fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _, config) - } - - private predicate readStepFwd( - NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config - ) { - fwdFlowRead(ap1, c, n1, n2, _, _, _, _, config) and - fwdFlowConsCand(ap1, c, ap2, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough0( - DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, - ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, - innerArgApa, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough( - RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, - Ap ap, Configuration config - ) { - exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | - returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa, config) and - pos = ret.getReturnPosition() and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowThroughIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap, - Configuration config - ) { - exists(ApApprox argApa | - flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), - allowsFieldFlow, argApa, pragma[only_bind_into](config)) and - fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa, - pragma[only_bind_into](config)) and - returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap, - pragma[only_bind_into](config)) and - if allowsFieldFlow = false then argAp instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowIntoCallAp( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap, - Configuration config - ) { - exists(ApApprox apa | - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - fwdFlow(arg, _, _, _, _, ap, apa, config) - ) - } - - pragma[nomagic] - private predicate flowOutOfCallAp( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, - Ap ap, Configuration config - ) { - exists(ApApprox apa | - flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa, config) and - fwdFlow(ret, _, _, _, _, ap, apa, config) and - pos = ret.getReturnPosition() - ) - } - - /** - * Holds if `node` with access path `ap` is part of a path from a source to a - * sink in the configuration `config`. - * - * The parameter `returnCtx` records whether (and how) the node must be returned - * from the enclosing callable in order to reach a sink, and if so, `returnAp` - * records the access path of the returned value. - */ - pragma[nomagic] - additional predicate revFlow( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - revFlow0(node, state, returnCtx, returnAp, ap, config) and - fwdFlow(node, state, _, _, _, ap, config) - } - - pragma[nomagic] - private predicate revFlow0( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, _, _, _, ap, config) and - sinkNode(node, state, config) and - ( - if hasSinkCallCtx(config) - then returnCtx = TReturnCtxNoFlowThrough() - else returnCtx = TReturnCtxNone() - ) and - returnAp = apNone() and - ap instanceof ApNil - or - exists(NodeEx mid, FlowState state0 | - localStep(node, state, mid, state0, true, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, ap, config) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap, pragma[only_bind_into](config)) and - localStep(node, pragma[only_bind_into](state), mid, state0, false, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, nil, pragma[only_bind_into](config)) and - ap instanceof ApNil - ) - or - exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, state, _, _, ap, config) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStep(node, mid, config) and - revFlow(pragma[only_bind_into](mid), state, _, _, nil, pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStateStep(node, state, mid, state0, config) and - revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil, - pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - // store - exists(Ap ap0, Content c | - revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp, config) and - revFlowConsCand(ap0, c, ap, config) - ) - or - // read - exists(NodeEx mid, Ap ap0 | - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - readStepFwd(node, ap, _, mid, ap0, config) - ) - or - // flow into a callable - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, state, TReturnCtxNone(), returnAp, ap, config) and - flowIntoCallAp(_, node, p, allowsFieldFlow, ap, config) and - (if allowsFieldFlow = false then ap instanceof ApNil else any()) and - returnCtx = TReturnCtxNone() - ) - or - // flow through a callable - exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, node, p, _, ap, innerReturnAp, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(_, node, pos, state, _, _, ap, config) and - if returnFlowsThrough(node, pos, state, _, _, _, ap, config) - then ( - returnCtx = TReturnCtxMaybeFlowThrough(pos) and - returnAp = apSome(ap) - ) else ( - returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() - ) - ) - } - - pragma[nomagic] - private predicate revFlowStore( - Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, - ReturnCtx returnCtx, ApOption returnAp, Configuration config - ) { - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - storeStepFwd(node, ap, tc, mid, ap0, config) and - tc.getContent() = c - } - - /** - * Holds if reverse flow with access path `tail` reaches a read of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(NodeEx mid, Ap tail0 | - revFlow(mid, _, _, _, tail, config) and - tail = pragma[only_bind_into](tail0) and - readStepFwd(_, cons, c, mid, tail0, config) - ) - } - - pragma[nomagic] - private predicate revFlowOut( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, - ApOption returnAp, Ap ap, Configuration config - ) { - exists(NodeEx out, boolean allowsFieldFlow | - revFlow(out, state, returnCtx, returnAp, ap, config) and - flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate revFlowParamToReturn( - ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap, Configuration config - ) { - revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), - pragma[only_bind_into](ap), pragma[only_bind_into](config)) and - parameterFlowThroughAllowed(p, pos.getKind()) and - PrevStage::parameterMayFlowThrough(p, getApprox(ap), config) - } - - pragma[nomagic] - private predicate revFlowThrough( - DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, - ApOption returnAp, Ap ap, Ap innerReturnAp, Configuration config - ) { - revFlowParamToReturn(p, state, pos, innerReturnAp, ap, config) and - revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned( - DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap, - Configuration config - ) { - exists(RetNodeEx ret, FlowState state, CcCall ccc | - revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap, config) and - returnFlowsThrough(ret, pos, state, ccc, _, _, ap, config) and - matchesCall(ccc, call) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Ap ap2, Content c | - PrevStage::storeStepCand(node1, _, tc, node2, contentType, config) and - revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _, config) and - revFlowConsCand(ap2, c, ap1, config) - ) - } - - predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(Ap ap1, Ap ap2 | - revFlow(node2, _, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and - readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _, - pragma[only_bind_into](config)) - ) - } - - additional predicate revFlow(NodeEx node, FlowState state, Configuration config) { - revFlow(node, state, _, _, _, config) - } - - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, _, _, ap, config) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, _, _, _, ap, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, Configuration config) { - revFlow(node, _, _, _, _, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, ap, config) - } - - private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepFwd(_, ap, tc, _, _, config) - } - - private predicate revConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepCand(_, ap, tc, _, _, config) - } - - private predicate validAp(Ap ap, Configuration config) { - revFlow(_, _, _, _, ap, config) and ap instanceof ApNil - or - exists(TypedContent head, Ap tail | - consCand(head, tail, config) and - ap = apCons(head, tail) - ) - } - - additional predicate consCand(TypedContent tc, Ap ap, Configuration config) { - revConsCand(tc, ap, config) and - validAp(ap, config) - } - - pragma[nomagic] - private predicate parameterFlowsThroughRev( - ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp, Configuration config - ) { - revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap, config) and - parameterFlowThroughAllowed(p, pos.getKind()) - } - - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(ReturnPosition pos | - returnFlowsThrough(_, pos, _, _, p, ap, _, config) and - parameterFlowsThroughRev(p, ap, pos, _, config) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - exists(ParamNodeEx p, ReturnPosition pos | - returnFlowsThrough(ret, pos, _, _, p, argAp, ap, config) and - parameterFlowsThroughRev(p, argAp, pos, ap, config) and - kind = pos.getKind() - ) - } - - pragma[nomagic] - private predicate revFlowThroughArg( - DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, - Ap ap, Configuration config - ) { - exists(ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp, config) - ) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | - revFlow(arg, state, returnCtx, returnAp, ap, config) and - revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, config)) and - fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and - states = count(FlowState state | fwdFlow(_, state, _, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | - fwdFlow(n, state, cc, summaryCtx, argAp, ap, config) - ) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, _, _, _, config)) and - fields = count(TypedContent f0 | consCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and - states = count(FlowState state | revFlow(_, state, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | - revFlow(n, state, returnCtx, retAp, ap, config) - ) - } - /* End: Stage logic. */ - } -} - -private module BooleanCallContext { - class Cc extends boolean { - Cc() { this in [true, false] } - } - - class CcCall extends Cc { - CcCall() { this = true } - } - - /** Holds if the call context may be `call`. */ - predicate matchesCall(CcCall cc, DataFlowCall call) { any() } - - class CcNoCall extends Cc { - CcNoCall() { this = false } - } - - Cc ccNone() { result = false } - - CcCall ccSomeCall() { result = true } - - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } -} - -private module Level1CallContext { - class Cc = CallContext; - - class CcCall = CallContextCall; - - pragma[inline] - predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } - - class CcNoCall = CallContextNoCall; - - Cc ccNone() { result instanceof CallContextAny } - - CcCall ccSomeCall() { result instanceof CallContextSomeCall } - - module NoLocalCallContext { - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSiteDispatch(call, c) - then result = TSpecificCall(call) - else result = TSomeCall() - } - } - - module LocalCallContext { - class LocalCc = LocalCallContext; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { - result = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - node.getEnclosingCallable()) - } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() - } - } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { - checkCallContextReturn(innercc, c, call) and - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() - } -} - -private module Stage2Param implements MkStage::StageParam { - private module PrevStage = Stage1; - - class Ap extends boolean { - Ap() { this in [true, false] } - } - - class ApNil extends Ap { - ApNil() { this = false } - } - - bindingset[result, ap] - PrevStage::Ap getApprox(Ap ap) { any() } - - ApNil getApNil(NodeEx node) { Stage1::revFlow(node, _) and exists(result) } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } - - class ApHeadContent = Unit; - - pragma[inline] - ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } - - ApHeadContent projectToHeadContent(Content c) { any() } - - class ApOption = BooleanOption; - - ApOption apNone() { result = TBooleanNone() } - - ApOption apSome(Ap ap) { result = TBooleanSome(ap) } - - import Level1CallContext - import NoLocalCallContext - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - ( - preservesValue = true and - localFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalStateStep(node1, state1, node2, state2, config) - ) and - exists(ap) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand1/6; - - predicate flowIntoCall = flowIntoCallNodeCand1/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - expectsContentEx(node, c) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - PrevStage::revFlowState(state, pragma[only_bind_into](config)) and - exists(ap) and - not stateBarrier(node, state, config) and - ( - notExpectsContent(node) - or - ap = true and - expectsContentCand(node, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage2 implements StageSig { - import MkStage::Stage -} - -pragma[nomagic] -private predicate flowOutOfCallNodeCand2( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -pragma[nomagic] -private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -private module LocalFlowBigStep { - /** - * A node where some checking is required, and hence the big-step relation - * is not allowed to step over. - */ - private class FlowCheckNode extends NodeEx { - FlowCheckNode() { - castNode(this.asNode()) or - clearsContentCached(this.asNode(), _) or - expectsContentCached(this.asNode(), _) - } - } - - /** - * Holds if `node` can be the first node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowEntry(NodeEx node, FlowState state, Configuration config) { - Stage2::revFlow(node, state, config) and - ( - sourceNode(node, state, config) - or - jumpStep(_, node, config) - or - additionalJumpStep(_, node, config) - or - additionalJumpStateStep(_, _, node, state, config) - or - node instanceof ParamNodeEx - or - node.asNode() instanceof OutNodeExt - or - Stage2::storeStepCand(_, _, _, node, _, config) - or - Stage2::readStepCand(_, _, node, config) - or - node instanceof FlowCheckNode - or - exists(FlowState s | - additionalLocalStateStep(_, s, node, state, config) and - s != state - ) - ) - } - - /** - * Holds if `node` can be the last node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowExit(NodeEx node, FlowState state, Configuration config) { - exists(NodeEx next | Stage2::revFlow(next, state, config) | - jumpStep(node, next, config) or - additionalJumpStep(node, next, config) or - flowIntoCallNodeCand2(_, node, next, _, config) or - flowOutOfCallNodeCand2(_, node, _, next, _, config) or - Stage2::storeStepCand(node, _, _, next, _, config) or - Stage2::readStepCand(node, _, next, config) - ) - or - exists(NodeEx next, FlowState s | Stage2::revFlow(next, s, config) | - additionalJumpStateStep(node, state, next, s, config) - or - additionalLocalStateStep(node, state, next, s, config) and - s != state - ) - or - Stage2::revFlow(node, state, config) and - node instanceof FlowCheckNode - or - sinkNode(node, state, config) - } - - pragma[noinline] - private predicate additionalLocalFlowStepNodeCand2( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, Configuration config - ) { - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 and - Stage2::revFlow(node1, pragma[only_bind_into](state1), false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false, - pragma[only_bind_into](config)) - or - additionalLocalStateStep(node1, state1, node2, state2, config) and - Stage2::revFlow(node1, state1, false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, state2, false, pragma[only_bind_into](config)) - } - - /** - * Holds if the local path from `node1` to `node2` is a prefix of a maximal - * subsequence of local flow steps in a dataflow path. - * - * This is the transitive closure of `[additional]localFlowStep` beginning - * at `localFlowEntry`. - */ - pragma[nomagic] - private predicate localFlowStepPlus( - NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, - Configuration config, LocalCallContext cc - ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and - ( - localFlowEntry(node1, pragma[only_bind_into](state), pragma[only_bind_into](config)) and - ( - localFlowStepNodeCand1(node1, node2, config) and - preservesValue = true and - t = node1.getDataFlowType() and // irrelevant dummy value - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - or - additionalLocalFlowStepNodeCand2(node1, state, node2, state, config) and - preservesValue = false and - t = node2.getDataFlowType() - ) and - node1 != node2 and - cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) - or - exists(NodeEx mid | - localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, - pragma[only_bind_into](config), cc) and - localFlowStepNodeCand1(mid, node2, config) and - not mid instanceof FlowCheckNode and - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - ) - or - exists(NodeEx mid | - localFlowStepPlus(node1, state, mid, _, _, pragma[only_bind_into](config), cc) and - additionalLocalFlowStepNodeCand2(mid, state, node2, state, config) and - not mid instanceof FlowCheckNode and - preservesValue = false and - t = node2.getDataFlowType() - ) - ) - } - - /** - * Holds if `node1` can step to `node2` in one or more local steps and this - * path can occur as a maximal subsequence of local steps in a dataflow path. - */ - pragma[nomagic] - predicate localFlowBigStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - DataFlowType t, Configuration config, LocalCallContext callContext - ) { - localFlowStepPlus(node1, state1, node2, preservesValue, t, config, callContext) and - localFlowExit(node2, state1, config) and - state1 = state2 - or - additionalLocalFlowStepNodeCand2(node1, state1, node2, state2, config) and - state1 != state2 and - preservesValue = false and - t = node2.getDataFlowType() and - callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) - } -} - -private import LocalFlowBigStep - -private module Stage3Param implements MkStage::StageParam { - private module PrevStage = Stage2; - - class Ap = ApproxAccessPathFront; - - class ApNil = ApproxAccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } - - class ApHeadContent = ContentApprox; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - predicate projectToHeadContent = getContentApprox/1; - - class ApOption = ApproxAccessPathFrontOption; - - ApOption apNone() { result = TApproxAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } - - import BooleanCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApproxAccessPathFrontNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand2/6; - - predicate flowIntoCall = flowIntoCallNodeCand2/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getAHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage3 implements StageSig { - import MkStage::Stage -} - -private module Stage4Param implements MkStage::StageParam { - private module PrevStage = Stage3; - - class Ap = AccessPathFront; - - class ApNil = AccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathFrontOption; - - ApOption apNone() { result = TAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } - - import BooleanCallContext - - pragma[nomagic] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) and - exists(lcc) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clearSet(NodeEx node, ContentSet c, Configuration config) { - PrevStage::revFlow(node, config) and - clearsContentCached(node.asNode(), c) - } - - pragma[nomagic] - private predicate clearContent(NodeEx node, Content c, Configuration config) { - exists(ContentSet cs | - PrevStage::readStepCand(_, pragma[only_bind_into](c), _, pragma[only_bind_into](config)) and - c = cs.getAReadContent() and - clearSet(node, cs, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clear(NodeEx node, Ap ap, Configuration config) { - clearContent(node, ap.getHead().getContent(), config) - } - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - not clear(node, ap, config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage4 implements StageSig { - import MkStage::Stage -} - -/** - * Holds if `argApf` is recorded as the summary context for flow reaching `node` - * and remains relevant for the following pruning stage. - */ -private predicate flowCandSummaryCtx( - NodeEx node, FlowState state, AccessPathFront argApf, Configuration config -) { - exists(AccessPathFront apf | - Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf, config) and - Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf, - config) - ) -} - -/** - * Holds if a length 2 access path approximation with the head `tc` is expected - * to be expensive. - */ -private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { - exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf, config)) and - nodes = - strictcount(NodeEx n, FlowState state | - Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - or - flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - ) and - accessPathApproxCostLimits(apLimit, tupleLimit) and - apLimit < tails and - tupleLimit < (tails - 1) * nodes and - not tc.forceHighPrecision() - ) -} - -private newtype TAccessPathApprox = - TNil(DataFlowType t) or - TConsNil(TypedContent tc, DataFlowType t) { - Stage4::consCand(tc, TFrontNil(t), _) and - not expensiveLen2unfolding(tc, _) - } or - TConsCons(TypedContent tc1, TypedContent tc2, int len) { - Stage4::consCand(tc1, TFrontHead(tc2), _) and - len in [2 .. accessPathLimit()] and - not expensiveLen2unfolding(tc1, _) - } or - TCons1(TypedContent tc, int len) { - len in [1 .. accessPathLimit()] and - expensiveLen2unfolding(tc, _) - } - -/** - * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only - * the first two elements of the list and its length are tracked. If data flows - * from a source to a given node with a given `AccessPathApprox`, this indicates - * the sequence of dereference operations needed to get from the value in the node - * to the tracked object. The final type indicates the type of the tracked object. - */ -abstract private class AccessPathApprox extends TAccessPathApprox { - abstract string toString(); - - abstract TypedContent getHead(); - - abstract int len(); - - abstract DataFlowType getType(); - - abstract AccessPathFront getFront(); - - /** Gets the access path obtained by popping `head` from this path, if any. */ - abstract AccessPathApprox pop(TypedContent head); -} - -private class AccessPathApproxNil extends AccessPathApprox, TNil { - private DataFlowType t; - - AccessPathApproxNil() { this = TNil(t) } - - override string toString() { result = concat(": " + ppReprType(t)) } - - override TypedContent getHead() { none() } - - override int len() { result = 0 } - - override DataFlowType getType() { result = t } - - override AccessPathFront getFront() { result = TFrontNil(t) } - - override AccessPathApprox pop(TypedContent head) { none() } -} - -abstract private class AccessPathApproxCons extends AccessPathApprox { } - -private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { - private TypedContent tc; - private DataFlowType t; - - AccessPathApproxConsNil() { this = TConsNil(tc, t) } - - override string toString() { - // The `concat` becomes "" if `ppReprType` has no result. - result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) - } - - override TypedContent getHead() { result = tc } - - override int len() { result = 1 } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } -} - -private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { - private TypedContent tc1; - private TypedContent tc2; - private int len; - - AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - - override string toString() { - if len = 2 - then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" - else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc1 } - - override int len() { result = len } - - override DataFlowType getType() { result = tc1.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc1) } - - override AccessPathApprox pop(TypedContent head) { - head = tc1 and - ( - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - } -} - -private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { - private TypedContent tc; - private int len; - - AccessPathApproxCons1() { this = TCons1(tc, len) } - - override string toString() { - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc } - - override int len() { result = len } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { - head = tc and - ( - exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2), _) | - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - or - exists(DataFlowType t | - len = 1 and - Stage4::consCand(tc, TFrontNil(t), _) and - result = TNil(t) - ) - ) - } -} - -/** Gets the access path obtained by popping `tc` from `ap`, if any. */ -private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } - -/** Gets the access path obtained by pushing `tc` onto `ap`. */ -private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } - -private newtype TAccessPathApproxOption = - TAccessPathApproxNone() or - TAccessPathApproxSome(AccessPathApprox apa) - -private class AccessPathApproxOption extends TAccessPathApproxOption { - string toString() { - this = TAccessPathApproxNone() and result = "" - or - this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) - } -} - -private module Stage5Param implements MkStage::StageParam { - private module PrevStage = Stage4; - - class Ap = AccessPathApprox; - - class ApNil = AccessPathApproxNil; - - pragma[nomagic] - PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathApproxOption; - - ApOption apNone() { result = TAccessPathApproxNone() } - - ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } - - import Level1CallContext - import LocalCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, lcc) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { any() } - - // Type checking is not necessary here as it has already been done in stage 3. - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage5 = MkStage::Stage; - -bindingset[conf, result] -private Configuration unbindConf(Configuration conf) { - exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c)) -} - -pragma[nomagic] -private predicate nodeMayUseSummary0( - NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(AccessPathApprox apa0 | - Stage5::parameterMayFlowThrough(p, _, _) and - Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0, config) and - Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), - TAccessPathApproxSome(apa), apa0, config) - ) -} - -pragma[nomagic] -private predicate nodeMayUseSummary( - NodeEx n, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::parameterMayFlowThrough(p, apa, config) and - nodeMayUseSummary0(n, p, state, apa, config) - ) -} - -private newtype TSummaryCtx = - TSummaryCtxNone() or - TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { - exists(Configuration config | - Stage5::parameterMayFlowThrough(p, ap.getApprox(), config) and - Stage5::revFlow(p, state, _, config) - ) - } - -/** - * A context for generating flow summaries. This represents flow entry through - * a specific parameter with an access path of a specific shape. - * - * Summaries are only created for parameters that may flow through. - */ -abstract private class SummaryCtx extends TSummaryCtx { - abstract string toString(); -} - -/** A summary context from which no flow summary can be generated. */ -private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { - override string toString() { result = "" } -} - -/** A summary context from which a flow summary can be generated. */ -private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParamNodeEx p; - private FlowState s; - private AccessPath ap; - - SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } - - ParameterPosition getParameterPos() { p.isParameterOf(_, result) } - - ParamNodeEx getParamNode() { result = p } - - override string toString() { result = p + ": " + ap } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** - * Gets the number of length 2 access path approximations that correspond to `apa`. - */ -private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { - exists(TypedContent tc, int len | - tc = apa.getHead() and - len = apa.len() and - result = - strictcount(AccessPathFront apf | - Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), - config) - ) - ) -} - -private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = - strictcount(NodeEx n, FlowState state | - Stage5::revFlow(n, state, apa, config) or nodeMayUseSummary(n, state, apa, config) - ) -} - -/** - * Holds if a length 2 access path approximation matching `apa` is expected - * to be expensive. - */ -private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = count1to2unfold(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - apLimit < aps and - tupleLimit < (aps - 1) * nodes - ) -} - -private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { - exists(TypedContent head | - apa.pop(head) = result and - Stage5::consCand(head, result, config) - ) -} - -/** - * Holds with `unfold = false` if a precise head-tail representation of `apa` is - * expected to be expensive. Holds with `unfold = true` otherwise. - */ -private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { - if apa.getHead().forceHighPrecision() - then unfold = true - else - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = countPotentialAps(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true - ) -} - -/** - * Gets the number of `AccessPath`s that correspond to `apa`. - */ -pragma[assume_small_delta] -private int countAps(AccessPathApprox apa, Configuration config) { - evalUnfold(apa, false, config) and - result = 1 and - (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) - or - evalUnfold(apa, false, config) and - result = count1to2unfold(apa, config) and - not expensiveLen1to2unfolding(apa, config) - or - evalUnfold(apa, true, config) and - result = countPotentialAps(apa, config) -} - -/** - * Gets the number of `AccessPath`s that would correspond to `apa` assuming - * that it is expanded to a precise head-tail representation. - */ -language[monotonicAggregates] -pragma[assume_small_delta] -private int countPotentialAps(AccessPathApprox apa, Configuration config) { - apa instanceof AccessPathApproxNil and result = 1 - or - result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) -} - -private newtype TAccessPath = - TAccessPathNil(DataFlowType t) or - TAccessPathCons(TypedContent head, AccessPath tail) { - exists(AccessPathApproxCons apa | - not evalUnfold(apa, false, _) and - head = apa.getHead() and - tail.getApprox() = getATail(apa, _) - ) - } or - TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - not expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head1 = apa.getHead() and - head2 = getATail(apa, _).getHead() - ) - } or - TAccessPathCons1(TypedContent head, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head = apa.getHead() - ) - } - -private newtype TPathNode = - pragma[assume_small_delta] - TPathNodeMid( - NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config - ) { - // A PathNode is introduced by a source ... - Stage5::revFlow(node, state, config) and - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - or - // ... or a step from an existing PathNode to another node. - exists(PathNodeMid mid | - pathStep(mid, node, state, cc, sc, ap) and - pragma[only_bind_into](config) = mid.getConfiguration() and - Stage5::revFlow(node, state, ap.getApprox(), pragma[only_bind_into](config)) - ) - } or - TPathNodeSink(NodeEx node, FlowState state, Configuration config) { - exists(PathNodeMid sink | - sink.isAtSink() and - node = sink.getNodeEx() and - state = sink.getState() and - config = sink.getConfiguration() - ) - } or - TPathNodeSourceGroup(string sourceGroup, Configuration config) { - exists(PathNodeImpl source | - sourceGroup = source.getSourceGroup() and - config = source.getConfiguration() - ) - } or - TPathNodeSinkGroup(string sinkGroup, Configuration config) { - exists(PathNodeSink sink | - sinkGroup = sink.getSinkGroup() and - config = sink.getConfiguration() - ) - } - -/** - * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a - * source to a given node with a given `AccessPath`, this indicates the sequence - * of dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ -private class AccessPath extends TAccessPath { - /** Gets the head of this access path, if any. */ - abstract TypedContent getHead(); - - /** Gets the tail of this access path, if any. */ - abstract AccessPath getTail(); - - /** Gets the front of this access path. */ - abstract AccessPathFront getFront(); - - /** Gets the approximation of this access path. */ - abstract AccessPathApprox getApprox(); - - /** Gets the length of this access path. */ - abstract int length(); - - /** Gets a textual representation of this access path. */ - abstract string toString(); - - /** Gets the access path obtained by popping `tc` from this access path, if any. */ - final AccessPath pop(TypedContent tc) { - result = this.getTail() and - tc = this.getHead() - } - - /** Gets the access path obtained by pushing `tc` onto this access path. */ - final AccessPath push(TypedContent tc) { this = result.pop(tc) } -} - -private class AccessPathNil extends AccessPath, TAccessPathNil { - private DataFlowType t; - - AccessPathNil() { this = TAccessPathNil(t) } - - DataFlowType getType() { result = t } - - override TypedContent getHead() { none() } - - override AccessPath getTail() { none() } - - override AccessPathFrontNil getFront() { result = TFrontNil(t) } - - override AccessPathApproxNil getApprox() { result = TNil(t) } - - override int length() { result = 0 } - - override string toString() { result = concat(": " + ppReprType(t)) } -} - -private class AccessPathCons extends AccessPath, TAccessPathCons { - private TypedContent head; - private AccessPath tail; - - AccessPathCons() { this = TAccessPathCons(head, tail) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { result = tail } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - pragma[assume_small_delta] - override AccessPathApproxCons getApprox() { - result = TConsNil(head, tail.(AccessPathNil).getType()) - or - result = TConsCons(head, tail.getHead(), this.length()) - or - result = TCons1(head, this.length()) - } - - pragma[assume_small_delta] - override int length() { result = 1 + tail.length() } - - private string toStringImpl(boolean needsSuffix) { - exists(DataFlowType t | - tail = TAccessPathNil(t) and - needsSuffix = false and - result = head.toString() + "]" + concat(" : " + ppReprType(t)) - ) - or - result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) - or - exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | - result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true - or - result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false - ) - or - exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | - result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true - or - result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false - ) - } - - override string toString() { - result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" - or - result = "[" + this.toStringImpl(false) - } -} - -private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { - private TypedContent head1; - private TypedContent head2; - private int len; - - AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } - - override TypedContent getHead() { result = head1 } - - override AccessPath getTail() { - Stage5::consCand(head1, result.getApprox(), _) and - result.getHead() = head2 and - result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head1) } - - override AccessPathApproxCons getApprox() { - result = TConsCons(head1, head2, len) or - result = TCons1(head1, len) - } - - override int length() { result = len } - - override string toString() { - if len = 2 - then result = "[" + head1.toString() + ", " + head2.toString() + "]" - else - result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" - } -} - -private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { - private TypedContent head; - private int len; - - AccessPathCons1() { this = TAccessPathCons1(head, len) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { - Stage5::consCand(head, result.getApprox(), _) and result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - - override int length() { result = len } - - override string toString() { - if len = 1 - then result = "[" + head.toString() + "]" - else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" - } -} - -abstract private class PathNodeImpl extends TPathNode { - /** Gets the `FlowState` of this node. */ - abstract FlowState getState(); - - /** Gets the associated configuration. */ - abstract Configuration getConfiguration(); - - /** Holds if this node is a source. */ - abstract predicate isSource(); - - abstract PathNodeImpl getASuccessorImpl(); - - private PathNodeImpl getASuccessorIfHidden() { - this.isHidden() and - result = this.getASuccessorImpl() - } - - pragma[nomagic] - private PathNodeImpl getANonHiddenSuccessor0() { - result = this.getASuccessorIfHidden*() and - not result.isHidden() - } - - final PathNodeImpl getANonHiddenSuccessor() { - result = this.getASuccessorImpl().getANonHiddenSuccessor0() and - not this.isHidden() - } - - abstract NodeEx getNodeEx(); - - predicate isHidden() { - not this.getConfiguration().includeHiddenNodes() and - ( - hiddenNode(this.getNodeEx().asNode()) and - not this.isSource() and - not this instanceof PathNodeSink - or - this.getNodeEx() instanceof TNodeImplicitRead - ) - } - - string getSourceGroup() { - this.isSource() and - this.getConfiguration().sourceGrouping(this.getNodeEx().asNode(), result) + not singleConfiguration() and + getConfig(state1).isAdditionalFlowStep(node1, node2) and + state2 = state1 } - predicate isFlowSource() { - this.isSource() and not exists(this.getSourceGroup()) - or - this instanceof PathNodeSourceGroup + predicate allowImplicitRead(Node node, ContentSet c) { + any(Configuration config).allowImplicitRead(node, c) } - predicate isFlowSink() { - this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or - this instanceof PathNodeSinkGroup - } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } - private string ppAp() { - this instanceof PathNodeSink and result = "" - or - exists(string s | s = this.(PathNodeMid).getAp().toString() | - if s = "" then result = "" else result = " " + s - ) - } + FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } - private string ppCtx() { - this instanceof PathNodeSink and result = "" - or - result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + predicate sourceGrouping(Node source, string sourceGroup) { + any(Configuration config).sourceGrouping(source, sourceGroup) } - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + predicate sinkGrouping(Node sink, string sinkGroup) { + any(Configuration config).sinkGrouping(sink, sinkGroup) } -} - -/** Holds if `n` can reach a sink. */ -private predicate directReach(PathNodeImpl n) { - n instanceof PathNodeSink or - n instanceof PathNodeSinkGroup or - directReach(n.getANonHiddenSuccessor()) -} - -/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ -private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } -/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { - n1.getANonHiddenSuccessor() = n2 and directReach(n2) + predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() } } -private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) +private import Impl as I +import I /** * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. */ -class PathNode instanceof PathNodeImpl { - PathNode() { reach(this) } - +class PathNode instanceof I::PathNode { /** Gets a textual representation of this element. */ final string toString() { result = super.toString() } @@ -3406,1548 +358,39 @@ class PathNode instanceof PathNodeImpl { } /** Gets the underlying `Node`. */ - final Node getNode() { super.getNodeEx().projectToNode() = result } + final Node getNode() { result = super.getNode() } /** Gets the `FlowState` of this node. */ - final FlowState getState() { result = super.getState() } + final FlowState getState() { result = getState(super.getState()) } /** Gets the associated configuration. */ - final Configuration getConfiguration() { result = super.getConfiguration() } + final Configuration getConfiguration() { result = getConfig(super.getState()) } /** Gets a successor of this node, if any. */ - final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } + final PathNode getASuccessor() { result = super.getASuccessor() } /** Holds if this node is a source. */ final predicate isSource() { super.isSource() } /** Holds if this node is a grouping of source nodes. */ - final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group, _) } + final predicate isSourceGroup(string group) { super.isSourceGroup(group) } /** Holds if this node is a grouping of sink nodes. */ - final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group, _) } + final predicate isSinkGroup(string group) { super.isSinkGroup(group) } } -/** - * Provides the query predicates needed to include a graph in a path-problem query. - */ -module PathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } - - /** Holds if `n` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode n, string key, string val) { - key = "semmle.label" and val = n.toString() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { - Subpaths::subpaths(arg, par, ret, out) - } -} - -/** - * An intermediate flow graph node. This is a triple consisting of a `Node`, - * a `CallContext`, and a `Configuration`. - */ -private class PathNodeMid extends PathNodeImpl, TPathNodeMid { - NodeEx node; - FlowState state; - CallContext cc; - SummaryCtx sc; - AccessPath ap; - Configuration config; - - PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - SummaryCtx getSummaryCtx() { result = sc } - - AccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - private PathNodeMid getSuccMid() { - pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx(), result.getAp()) and - result.getConfiguration() = unbindConf(this.getConfiguration()) - } - - override PathNodeImpl getASuccessorImpl() { - // an intermediate step to another intermediate node - result = this.getSuccMid() - or - // a final step to a sink - result = this.getSuccMid().projectToSink() - } - - override predicate isSource() { - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - } - - predicate isAtSink() { - sinkNode(node, state, config) and - ap instanceof AccessPathNil and - if hasSinkCallCtx(config) - then - // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` - // is exactly what we need to check. This also implies - // `sc instanceof SummaryCtxNone`. - // For `FeatureEqualSourceSinkCallContext` the initial call context was - // set to `CallContextSomeCall` and jumps are disallowed, so - // `cc instanceof CallContextNoCall` never holds. On the other hand, - // in this case there's never any need to enter a call except to identify - // a summary, so the condition in `pathIntoCallable` enforces this, which - // means that `sc instanceof SummaryCtxNone` holds if and only if we are - // in the call context of the source. - sc instanceof SummaryCtxNone or - cc instanceof CallContextNoCall - else any() - } - - PathNodeSink projectToSink() { - this.isAtSink() and - result.getNodeEx() = node and - result.getState() = state and - result.getConfiguration() = unbindConf(config) - } -} - -/** - * A flow graph node corresponding to a sink. This is disjoint from the - * intermediate nodes in order to uniquely correspond to a given sink by - * excluding the `CallContext`. - */ -private class PathNodeSink extends PathNodeImpl, TPathNodeSink { - NodeEx node; - FlowState state; - Configuration config; - - PathNodeSink() { this = TPathNodeSink(node, state, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result = TPathNodeSinkGroup(this.getSinkGroup(), config) - } - - override predicate isSource() { sourceNode(node, state, config) } - - string getSinkGroup() { config.sinkGrouping(node.asNode(), result) } -} - -private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { - string sourceGroup; - Configuration config; - - PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result.getSourceGroup() = sourceGroup and - result.getConfiguration() = config - } - - override predicate isSource() { none() } - - override string toString() { result = sourceGroup } - - override 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 - } -} - -private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { - string sinkGroup; - Configuration config; - - PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { none() } - - override predicate isSource() { none() } - - override string toString() { result = sinkGroup } - - override 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 - } -} - -private predicate pathNode( - PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, - Configuration conf, LocalCallContext localCC -) { - midnode = mid.getNodeEx() and - state = mid.getState() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - midnode.getEnclosingCallable()) and - ap = mid.getAp() -} - -/** - * Holds if data may flow from `mid` to `node`. The last step in or out of - * a callable is recorded by `cc`. - */ -pragma[assume_small_delta] -pragma[nomagic] -private predicate pathStep( - PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap -) { - exists(NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC | - pathNode(mid, midnode, state0, cc, sc, ap, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, true, _, conf, localCC) - ) - or - exists( - AccessPath ap0, NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC - | - pathNode(mid, midnode, state0, cc, sc, ap0, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), conf, - localCC) and - ap0 instanceof AccessPathNil - ) - or - jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - ap = mid.getAp() - or - additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, mid.getConfiguration()) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - pathIntoCallable(mid, node, state, _, cc, sc, _, _) and ap = mid.getAp() - or - pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone - or - pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() -} - -pragma[nomagic] -private predicate pathReadStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - tc = ap0.getHead() and - Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -pragma[nomagic] -private predicate pathStoreStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -private predicate pathOutOfCallable0( - PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa, - Configuration config -) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - apa = mid.getAp().getApprox() and - config = mid.getConfiguration() -} - -pragma[nomagic] -private predicate pathOutOfCallable1( - PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPathApprox apa, Configuration config -) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - pathOutOfCallable0(mid, pos, state, innercc, apa, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() +private predicate hasFlow(Node source, Node sink, Configuration config) { + exists(PathNode source0, PathNode sink0 | + hasFlowPath(source0, sink0, config) and + source0.getNode() = source and + sink0.getNode() = sink ) } -pragma[noinline] -private NodeEx getAnOutNodeFlow( - ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config -) { - result.asNode() = kind.getAnOutNode(call) and - Stage5::revFlow(result, _, apa, config) +private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) { + hasFlowPath(source, sink) and source.getConfiguration() = config } -/** - * Holds if data may flow from `mid` to `out`. The last step of this path - * is a return from a callable and is recorded by `cc`, if needed. - */ -pragma[noinline] -private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { - exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | - pathOutOfCallable1(mid, call, kind, state, cc, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} +private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) } -/** - * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. - */ -pragma[noinline] -private predicate pathIntoArg( - PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(ArgNodeEx arg, ArgumentPosition apos | - pathNode(mid, arg, state, cc, _, ap, config, _) and - arg.asNode().(ArgNode).argumentOf(call, apos) and - apa = ap.getApprox() and - parameterMatch(ppos, apos) - ) -} - -pragma[nomagic] -private predicate parameterCand( - DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::revFlow(p, _, apa, config) and - p.isParameterOf(callable, pos) - ) -} - -pragma[nomagic] -private predicate pathIntoCallable0( - PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, AccessPath ap, Configuration config -) { - exists(AccessPathApprox apa | - pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - callable = resolveCall(call, outercc) and - parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa), - pragma[only_bind_into](config)) - ) -} - -/** - * Holds if data may flow from `mid` to `p` through `call`. The contexts - * before and after entering the callable are `outercc` and `innercc`, - * respectively. - */ -pragma[nomagic] -private predicate pathIntoCallable( - PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, - SummaryCtx sc, DataFlowCall call, Configuration config -) { - exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | - pathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - ( - sc = TSummaryCtxSome(p, state, ap) - or - not exists(TSummaryCtxSome(p, state, ap)) and - sc = TSummaryCtxNone() and - // When the call contexts of source and sink needs to match then there's - // never any reason to enter a callable except to find a summary. See also - // the comment in `PathNodeMid::isAtSink`. - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) -} - -/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ -pragma[nomagic] -private predicate paramFlowsThrough( - ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, - AccessPathApprox apa, Configuration config -) { - exists(RetNodeEx ret | - pathNode(_, ret, state, cc, sc, ap, config, _) and - kind = ret.getKind() and - apa = ap.getApprox() and - parameterFlowThroughAllowed(sc.getParamNode(), kind) - ) -} - -pragma[nomagic] -private predicate pathThroughCallable0( - DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(CallContext innercc, SummaryCtx sc | - pathIntoCallable(mid, _, _, cc, innercc, sc, call, config) and - paramFlowsThrough(kind, state, innercc, sc, ap, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` through a callable to the node `out`. - * The context `cc` is restored to its value prior to entering the callable. - */ -pragma[noinline] -private predicate pathThroughCallable( - PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap -) { - exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config | - pathThroughCallable0(call, mid, kind, state, cc, ap, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -private module Subpaths { - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths01( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - exists(Configuration config | - pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and - pathIntoCallable(arg, par, _, _, innercc, sc, _, config) and - paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, - pragma[only_bind_into](apout), _, unbindConf(config)) and - not arg.isHidden() - ) - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `sout`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths02( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and - out.asNode() = kind.getAnOutNode(_) - } - - pragma[nomagic] - private Configuration getPathNodeConf(PathNodeImpl n) { result = n.getConfiguration() } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple. - */ - pragma[nomagic] - private predicate subpaths03( - PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, AccessPath apout - ) { - exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | - subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and - pathNode(ret, retnode, sout, innercc, sc, apout, unbindConf(getPathNodeConf(arg)), _) and - kind = retnode.getKind() - ) - } - - private PathNodeImpl localStepToHidden(PathNodeImpl n) { - n.getASuccessorImpl() = result and - result.isHidden() and - exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | - localFlowBigStep(n1, _, n2, _, _, _, _, _) or - store(n1, _, n2, _, _) or - readSet(n1, _, n2, _) - ) - } - - pragma[nomagic] - private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { - succ = pred.getANonHiddenSuccessor() and - succNode = succ.getNodeEx() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { - exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | - pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and - subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and - hasSuccessor(pragma[only_bind_into](arg), par, p) and - not ret.isHidden() and - pathNode(out0, o, sout, _, _, apout, _, _) - | - out = out0 or out = out0.projectToSink() - ) - } - - /** - * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. - */ - predicate retReach(PathNodeImpl n) { - exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) - or - exists(PathNodeImpl mid | - retReach(mid) and - n.getANonHiddenSuccessor() = mid and - not subpaths(_, mid, _, _) - ) - } -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -private predicate hasFlowPath( - PathNodeImpl flowsource, PathNodeImpl flowsink, Configuration configuration -) { - flowsource.isFlowSource() and - flowsource.getConfiguration() = configuration and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.isFlowSink() -} - -private predicate flowsTo( - PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink, - Configuration configuration -) { - flowsource.isSource() and - flowsource.getConfiguration() = configuration and - flowsource.getNodeEx().asNode() = source and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.getNodeEx().asNode() = sink -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -predicate flowsTo(Node source, Node sink, Configuration configuration) { - flowsTo(_, _, source, sink, configuration) -} - -private predicate finalStats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples -) { - fwd = true and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and - tuples = count(PathNodeImpl pn) - or - fwd = false and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and - tuples = count(PathNode pn) -} - -/** - * INTERNAL: Only for debugging. - * - * Calculates per-stage metrics for data flow. - */ -predicate stageStats( - int n, string stage, int nodes, int fields, int conscand, int states, int tuples, - Configuration config -) { - stage = "1 Fwd" and - n = 10 and - Stage1::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "1 Rev" and - n = 15 and - Stage1::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Fwd" and - n = 20 and - Stage2::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Rev" and - n = 25 and - Stage2::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Fwd" and - n = 30 and - Stage3::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Rev" and - n = 35 and - Stage3::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Fwd" and - n = 40 and - Stage4::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Rev" and - n = 45 and - Stage4::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Fwd" and - n = 50 and - Stage5::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Rev" and - n = 55 and - Stage5::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) - or - stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) -} - -private module FlowExploration { - private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - jumpStep(node1, node2, config) - or - additionalJumpStep(node1, node2, config) - or - additionalJumpStateStep(node1, _, node2, _, config) - or - // flow into callable - viableParamArgEx(_, node2, node1) - or - // flow out of a callable - viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) - | - c1 = node1.getEnclosingCallable() and - c2 = node2.getEnclosingCallable() and - c1 != c2 - ) - } - - private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSource(n) or config.isSource(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSrc(mid, config) and callableStep(mid, c, config) - ) - } - - private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSink(n) or config.isSink(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSink(mid, config) and callableStep(c, mid, config) - ) - } - - private newtype TCallableExt = - TCallable(DataFlowCallable c, Configuration config) { - interestingCallableSrc(c, config) or - interestingCallableSink(c, config) - } or - TCallableSrc() or - TCallableSink() - - private predicate callableExtSrc(TCallableSrc src) { any() } - - private predicate callableExtSink(TCallableSink sink) { any() } - - private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { - exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | - callableStep(c1, c2, config) and - ce1 = TCallable(c1, pragma[only_bind_into](config)) and - ce2 = TCallable(c2, pragma[only_bind_into](config)) - ) - or - exists(Node n, Configuration config | - ce1 = TCallableSrc() and - (config.isSource(n) or config.isSource(n, _)) and - ce2 = TCallable(getNodeEnclosingCallable(n), config) - ) - or - exists(Node n, Configuration config | - ce2 = TCallableSink() and - (config.isSink(n) or config.isSink(n, _)) and - ce1 = TCallable(getNodeEnclosingCallable(n), config) - ) - } - - private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { - callableExtStepFwd(ce2, ce1) - } - - private int distSrcExt(TCallableExt c) = - shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) - - private int distSinkExt(TCallableExt c) = - shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) - - private int distSrc(DataFlowCallable c, Configuration config) { - result = distSrcExt(TCallable(c, config)) - 1 - } - - private int distSink(DataFlowCallable c, Configuration config) { - result = distSinkExt(TCallable(c, config)) - 1 - } - - private newtype TPartialAccessPath = - TPartialNil(DataFlowType t) or - TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first - * element of the list and its length are tracked. If data flows from a source to - * a given node with a given `AccessPath`, this indicates the sequence of - * dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ - private class PartialAccessPath extends TPartialAccessPath { - abstract string toString(); - - TypedContent getHead() { this = TPartialCons(result, _) } - - int len() { - this = TPartialNil(_) and result = 0 - or - this = TPartialCons(_, result) - } - - DataFlowType getType() { - this = TPartialNil(result) - or - exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) - } - } - - private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { - override string toString() { - exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) - } - } - - private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { - override string toString() { - exists(TypedContent tc, int len | this = TPartialCons(tc, len) | - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private newtype TRevPartialAccessPath = - TRevPartialNil() or - TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `Content`s, but only the first - * element of the list and its length are tracked. - */ - private class RevPartialAccessPath extends TRevPartialAccessPath { - abstract string toString(); - - Content getHead() { this = TRevPartialCons(result, _) } - - int len() { - this = TRevPartialNil() and result = 0 - or - this = TRevPartialCons(_, result) - } - } - - private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { - override string toString() { result = "" } - } - - private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { - override string toString() { - exists(Content c, int len | this = TRevPartialCons(c, len) | - if len = 1 - then result = "[" + c.toString() + "]" - else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private predicate relevantState(FlowState state) { - sourceNode(_, state, _) or - sinkNode(_, state, _) or - additionalLocalStateStep(_, state, _, _, _) or - additionalLocalStateStep(_, _, _, state, _) or - additionalJumpStateStep(_, state, _, _, _) or - additionalJumpStateStep(_, _, _, state, _) - } - - private newtype TSummaryCtx1 = - TSummaryCtx1None() or - TSummaryCtx1Param(ParamNodeEx p) - - private newtype TSummaryCtx2 = - TSummaryCtx2None() or - TSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TSummaryCtx3 = - TSummaryCtx3None() or - TSummaryCtx3Some(PartialAccessPath ap) - - private newtype TRevSummaryCtx1 = - TRevSummaryCtx1None() or - TRevSummaryCtx1Some(ReturnPosition pos) - - private newtype TRevSummaryCtx2 = - TRevSummaryCtx2None() or - TRevSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TRevSummaryCtx3 = - TRevSummaryCtx3None() or - TRevSummaryCtx3Some(RevPartialAccessPath ap) - - private newtype TPartialPathNode = - TPartialPathNodeFwd( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = TPartialNil(node.getDataFlowType()) and - exists(config.explorationLimit()) - or - partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap, config) and - distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() - } or - TPartialPathNodeRev( - NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, - RevPartialAccessPath ap, Configuration config - ) { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() and - exists(config.explorationLimit()) - or - revPartialPathStep(_, node, state, sc1, sc2, sc3, ap, config) and - not clearsContentEx(node, ap.getHead()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead()) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() - } - - pragma[nomagic] - private predicate partialPathNodeMk0( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap, config) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - not clearsContentEx(node, ap.getHead().getContent()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead().getContent()) - ) and - if node.asNode() instanceof CastingNode - then compatibleTypes(node.getDataFlowType(), ap.getType()) - else any() - } - - /** - * A `Node` augmented with a call context, an access path, and a configuration. - */ - class PartialPathNode extends TPartialPathNode { - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() - } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - /** Gets the underlying `Node`. */ - final Node getNode() { this.getNodeEx().projectToNode() = result } - - FlowState getState() { none() } - - private NodeEx getNodeEx() { - result = this.(PartialPathNodeFwd).getNodeEx() or - result = this.(PartialPathNodeRev).getNodeEx() - } - - /** Gets the associated configuration. */ - Configuration getConfiguration() { none() } - - /** Gets a successor of this node, if any. */ - PartialPathNode getASuccessor() { none() } - - /** - * Gets the approximate distance to the nearest source measured in number - * of interprocedural steps. - */ - int getSourceDistance() { - result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - /** - * Gets the approximate distance to the nearest sink measured in number - * of interprocedural steps. - */ - int getSinkDistance() { - result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - private string ppAp() { - exists(string s | - s = this.(PartialPathNodeFwd).getAp().toString() or - s = this.(PartialPathNodeRev).getAp().toString() - | - if s = "" then result = "" else result = " " + s - ) - } - - private string ppCtx() { - result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" - } - - /** Holds if this is a source in a forward-flow path. */ - predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } - - /** Holds if this is a sink in a reverse-flow path. */ - predicate isRevSink() { this.(PartialPathNodeRev).isSink() } - } - - /** - * Provides the query predicates needed to include a graph in a path-problem query. - */ - module PartialPathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } - } - - private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { - NodeEx node; - FlowState state; - CallContext cc; - TSummaryCtx1 sc1; - TSummaryCtx2 sc2; - TSummaryCtx3 sc3; - PartialAccessPath ap; - Configuration config; - - PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - TSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TSummaryCtx3 getSummaryCtx3() { result = sc3 } - - PartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeFwd getASuccessor() { - partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp(), - result.getConfiguration()) - } - - predicate isSource() { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap instanceof TPartialNil - } - } - - private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { - NodeEx node; - FlowState state; - TRevSummaryCtx1 sc1; - TRevSummaryCtx2 sc2; - TRevSummaryCtx3 sc3; - RevPartialAccessPath ap; - Configuration config; - - PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } - - RevPartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeRev getASuccessor() { - revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), - this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp(), this.getConfiguration()) - } - - predicate isSink() { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() - } - } - - private predicate partialPathStep( - PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, - TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and - ( - localFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - ) - or - jumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - partialPathStoreStep(mid, _, _, node, ap) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(PartialAccessPath ap0, TypedContent tc | - partialPathReadStep(mid, ap0, tc, node, cc, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsFwd(ap, tc, ap0, config) - ) - or - partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap, config) - or - partialPathOutOfCallable(mid, node, state, cc, ap, config) and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() - or - partialPathThroughCallable(mid, node, state, cc, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - bindingset[result, i] - private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } - - pragma[inline] - private predicate partialPathStoreStep( - PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, - PartialAccessPath ap2 - ) { - exists(NodeEx midNode, DataFlowType contentType | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - store(midNode, tc, node, contentType, mid.getConfiguration()) and - ap2.getHead() = tc and - ap2.len() = unbindInt(ap1.len() + 1) and - compatibleTypes(ap1.getType(), contentType) - ) - } - - pragma[nomagic] - private predicate apConsFwd( - PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeFwd mid | - partialPathStoreStep(mid, ap1, tc, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate partialPathReadStep( - PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc, - Configuration config - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and - ap.getHead() = tc and - pragma[only_bind_into](config) = mid.getConfiguration() and - cc = mid.getCallContext() - ) - } - - private predicate partialPathOutOfCallable0( - PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, - PartialAccessPath ap, Configuration config - ) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - ap = mid.getAp() and - config = mid.getConfiguration() - } - - pragma[nomagic] - private predicate partialPathOutOfCallable1( - PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - partialPathOutOfCallable0(mid, pos, state, innercc, ap, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) - } - - private predicate partialPathOutOfCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(ReturnKindExt kind, DataFlowCall call | - partialPathOutOfCallable1(mid, call, kind, state, cc, ap, config) - | - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[noinline] - private predicate partialPathIntoArg( - PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ArgNode arg, ArgumentPosition apos | - arg = mid.getNodeEx().asNode() and - state = mid.getState() and - cc = mid.getCallContext() and - arg.argumentOf(call, apos) and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate partialPathIntoCallable0( - PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - partialPathIntoArg(mid, pos, state, outercc, call, ap, config) and - callable = resolveCall(call, outercc) - } - - private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, - CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ParameterPosition pos, DataFlowCallable callable | - partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - sc1 = TSummaryCtx1Param(p) and - sc2 = TSummaryCtx2Some(state) and - sc3 = TSummaryCtx3Some(ap) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) - } - - pragma[nomagic] - private predicate paramFlowsThroughInPartialPath( - ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeFwd mid, RetNodeEx ret | - mid.getNodeEx() = ret and - kind = ret.getKind() and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() and - ap = mid.getAp() - ) - } - - pragma[noinline] - private predicate partialPathThroughCallable0( - DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | - partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _, config) and - paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap, config) - ) - } - - private predicate partialPathThroughCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ReturnKindExt kind | - partialPathThroughCallable0(call, mid, kind, state, cc, ap, config) and - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[nomagic] - private predicate revPartialPathStep( - PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, - TRevSummaryCtx3 sc3, RevPartialAccessPath ap, Configuration config - ) { - localFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - jumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - revPartialPathReadStep(mid, _, _, node, ap) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(RevPartialAccessPath ap0, Content c | - revPartialPathStoreStep(mid, ap0, c, node, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsRev(ap, c, ap0, config) - ) - or - exists(ParamNodeEx p | - mid.getNodeEx() = p and - viableParamArgEx(_, p, node) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - or - exists(ReturnPosition pos | - revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap, config) and - pos = getReturnPosition(node.asNode()) - ) - or - revPartialPathThroughCallable(mid, node, state, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - pragma[inline] - private predicate revPartialPathReadStep( - PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, - RevPartialAccessPath ap2 - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - read(node, c, midNode, mid.getConfiguration()) and - ap2.getHead() = c and - ap2.len() = unbindInt(ap1.len() + 1) - ) - } - - pragma[nomagic] - private predicate apConsRev( - RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeRev mid | - revPartialPathReadStep(mid, ap1, c, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathStoreStep( - PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config - ) { - exists(NodeEx midNode, TypedContent tc | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - store(node, tc, midNode, _, config) and - ap.getHead() = c and - config = mid.getConfiguration() and - tc.getContent() = c - ) - } - - pragma[nomagic] - private predicate revPartialPathIntoReturn( - PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, - TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap, - Configuration config - ) { - exists(NodeEx out | - mid.getNodeEx() = out and - mid.getState() = state and - viableReturnPosOutEx(call, pos, out) and - sc1 = TRevSummaryCtx1Some(pos) and - sc2 = TRevSummaryCtx2Some(state) and - sc3 = TRevSummaryCtx3Some(ap) and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathFlowsThrough( - ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, - TRevSummaryCtx3Some sc3, RevPartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | - mid.getNodeEx() = p and - mid.getState() = state and - p.getPosition() = ppos and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable0( - DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, - RevPartialAccessPath ap, Configuration config - ) { - exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | - revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _, config) and - revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap, config) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ArgumentPosition pos | - revPartialPathThroughCallable0(call, mid, pos, state, ap, config) and - node.asNode().(ArgNode).argumentOf(call, pos) - ) - } -} - -import FlowExploration - -private predicate partialFlow( - PartialPathNode source, PartialPathNode node, Configuration configuration -) { - source.getConfiguration() = configuration and - source.isFwdSource() and - node = source.getASuccessor+() -} - -private predicate revPartialFlow( - PartialPathNode node, PartialPathNode sink, Configuration configuration -) { - sink.getConfiguration() = configuration and - sink.isRevSink() and - node.getASuccessor+() = sink -} +predicate flowsTo = hasFlow/3; diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl4.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl4.qll index 1b969756b09..e6bdc74cceb 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl4.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl4.qll @@ -1,16 +1,16 @@ /** - * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from - * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed - * through the `Configuration` class. This file exists in several identical - * copies, allowing queries to use multiple `Configuration` classes that depend - * on each other without introducing mutual recursion among those configurations. + * DEPRECATED: Use `Make` and `MakeWithState` instead. + * + * Provides a `Configuration` class backwards-compatible interface to the data + * flow library. */ private import DataFlowImplCommon private import DataFlowImplSpecific::Private import DataFlowImplSpecific::Public +private import DataFlowImpl import DataFlowImplCommonPublic +import FlowStateString /** * A configuration of interprocedural data flow analysis. This defines @@ -144,6 +144,10 @@ abstract class Configuration extends string { * - `FeatureEqualSourceSinkCallContext`: * Implies both of the above and additionally ensures that the entire flow * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. */ FlowFeature getAFeature() { none() } @@ -156,7 +160,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from `source` to `sink` for this configuration. */ - predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } + predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) } /** * Holds if data may flow from `source` to `sink` for this configuration. @@ -169,9 +173,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from some source to `sink` for this configuration. */ - predicate hasFlowTo(Node sink) { - sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode() - } + predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) } /** * Holds if data may flow from some source to `sink` for this configuration. @@ -179,10 +181,12 @@ abstract class Configuration extends string { predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } /** + * DEPRECATED: Use `FlowExploration` instead. + * * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` * measured in approximate number of interprocedural steps. */ - int explorationLimit() { none() } + deprecated int explorationLimit() { none() } /** * Holds if hidden nodes should be included in the data flow graph. @@ -191,49 +195,6 @@ abstract class Configuration extends string { * is not visualized (for example in a `path-problem` query). */ predicate includeHiddenNodes() { none() } - - /** - * Holds if there is a partial data flow path from `source` to `node`. The - * approximate distance between `node` and the closest source is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards sink definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sources is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - */ - final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { - partialFlow(source, node, this) and - dist = node.getSourceDistance() - } - - /** - * Holds if there is a partial data flow path from `node` to `sink`. The - * approximate distance between `node` and the closest sink is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards source definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sinks is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - * - * Note that reverse flow has slightly lower precision than the corresponding - * forward flow, as reverse flow disregards type pruning among other features. - */ - final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { - revPartialFlow(node, sink, this) and - dist = node.getSinkDistance() - } } /** @@ -263,90 +224,6 @@ abstract private class ConfigurationRecursionPrevention extends Configuration { } } -private newtype TNodeEx = - TNodeNormal(Node n) or - TNodeImplicitRead(Node n, boolean hasRead) { - any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true] - } - -private class NodeEx extends TNodeEx { - string toString() { - result = this.asNode().toString() - or - exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") - } - - Node asNode() { this = TNodeNormal(result) } - - predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } - - Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } - - pragma[nomagic] - private DataFlowCallable getEnclosingCallable0() { - nodeEnclosingCallable(this.projectToNode(), result) - } - - pragma[inline] - DataFlowCallable getEnclosingCallable() { - pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) - } - - pragma[nomagic] - private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } - - pragma[inline] - DataFlowType getDataFlowType() { - pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) - } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -private class ArgNodeEx extends NodeEx { - ArgNodeEx() { this.asNode() instanceof ArgNode } -} - -private class ParamNodeEx extends NodeEx { - ParamNodeEx() { this.asNode() instanceof ParamNode } - - predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { - this.asNode().(ParamNode).isParameterOf(c, pos) - } - - ParameterPosition getPosition() { this.isParameterOf(_, result) } -} - -private class RetNodeEx extends NodeEx { - RetNodeEx() { this.asNode() instanceof ReturnNodeExt } - - ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } - - ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } -} - -private predicate inBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierIn(n) - | - config.isSource(n) or config.isSource(n, _) - ) -} - -private predicate outBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierOut(n) - | - config.isSink(n) or config.isSink(n, _) - ) -} - /** A bridge class to access the deprecated `isBarrierGuard`. */ private class BarrierGuardGuardedNodeBridge extends Unit { abstract predicate guardedNode(Node n, Configuration config); @@ -370,3019 +247,94 @@ private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { } } -pragma[nomagic] -private predicate fullBarrier(NodeEx node, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n) +private FlowState relevantState(Configuration config) { + config.isSource(_, result) or + config.isSink(_, result) or + config.isBarrier(_, result) or + config.isAdditionalFlowStep(_, result, _, _) or + config.isAdditionalFlowStep(_, _, _, result) +} + +private newtype TConfigState = + TMkConfigState(Configuration config, FlowState state) { + state = relevantState(config) or state instanceof FlowStateEmpty + } + +private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) } + +private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) } + +private predicate singleConfiguration() { 1 = strictcount(Configuration c) } + +private module Config implements FullStateConfigSig { + class FlowState = TConfigState; + + predicate isSource(Node source, FlowState state) { + getConfig(state).isSource(source, getState(state)) or - config.isBarrierIn(n) and - not config.isSource(n) and - not config.isSource(n, _) + getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty + } + + predicate isSink(Node sink, FlowState state) { + getConfig(state).isSink(sink, getState(state)) or - config.isBarrierOut(n) and - not config.isSink(n) and - not config.isSink(n, _) + getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty + } + + predicate isBarrier(Node node) { none() } + + predicate isBarrier(Node node, FlowState state) { + getConfig(state).isBarrier(node, getState(state)) or + getConfig(state).isBarrier(node) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state)) + } + + predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) } + + predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) } + + predicate isAdditionalFlowStep(Node node1, Node node2) { + singleConfiguration() and + any(Configuration config).isAdditionalFlowStep(node1, node2) + } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and + getConfig(state2) = getConfig(state1) or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config) - ) -} - -pragma[nomagic] -private predicate stateBarrier(NodeEx node, FlowState state, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n, state) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config) - ) -} - -pragma[nomagic] -private predicate sourceNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSource(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSource(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -pragma[nomagic] -private predicate sinkNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSink(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSink(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -/** Provides the relevant barriers for a step from `node1` to `node2`. */ -pragma[inline] -private predicate stepFilter(NodeEx node1, NodeEx node2, Configuration config) { - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) -} - -/** - * Holds if data can flow in one local step from `node1` to `node2`. - */ -private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.asNode() = n and - node2.isImplicitReadNode(n, false) and - not fullBarrier(node1, config) - ) -} - -/** - * Holds if the additional step from `node1` to `node2` does not jump between callables. - */ -private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.isImplicitReadNode(n, true) and - node2.asNode() = n and - not fullBarrier(node2, config) - ) -} - -private predicate additionalLocalStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) - ) -} - -/** - * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. - */ -private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if the additional step from `node1` to `node2` jumps between callables. - */ -private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate additionalJumpStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -pragma[nomagic] -private predicate readSet(NodeEx node1, ContentSet c, NodeEx node2, Configuration config) { - readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and - stepFilter(node1, node2, config) - or - exists(Node n | - node2.isImplicitReadNode(n, true) and - node1.isImplicitReadNode(n, _) and - config.allowImplicitRead(n, c) - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(ContentSet cs | - readSet(node1, cs, node2, config) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate clearsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - clearsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate expectsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - expectsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -pragma[nomagic] -private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } - -pragma[nomagic] -private predicate hasReadStep(Content c, Configuration config) { read(_, c, _, config) } - -pragma[nomagic] -private predicate store( - NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config -) { - store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), - contentType) and - hasReadStep(tc.getContent(), config) and - stepFilter(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { - viableReturnPosOut(call, pos, out.asNode()) -} - -pragma[nomagic] -private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { - viableParamArg(call, p.asNode(), arg.asNode()) -} - -/** - * Holds if field flow should be used for the given configuration. - */ -private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } - -private predicate hasSourceCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSourceCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate hasSinkCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSinkCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if flow from `p` to a return node of kind `kind` is allowed. - * - * We don't expect a parameter to return stored in itself, unless - * explicitly allowed - */ -bindingset[p, kind] -private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { - exists(ParameterPosition pos | p.isParameterOf(_, pos) | - not kind.(ParamUpdateReturnKind).getPosition() = pos - or - allowParameterReturnInSelfCached(p.asNode()) - ) -} - -private module Stage1 implements StageSig { - class Ap extends int { - // workaround for bad functionality-induced joins (happens when using `Unit`) - pragma[nomagic] - Ap() { this in [0 .. 1] and this < 1 } - } - - private class Cc = boolean; - - /* Begin: Stage 1 logic. */ - /** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `cc` records whether the node is reached through an - * argument in a call. - */ - private predicate fwdFlow(NodeEx node, Cc cc, Configuration config) { - sourceNode(node, _, config) and - if hasSourceCallCtx(config) then cc = true else cc = false - or - exists(NodeEx mid | fwdFlow(mid, cc, config) | - localFlowStep(mid, node, config) or - additionalLocalFlowStep(mid, node, config) or - additionalLocalStateStep(mid, _, node, _, config) - ) - or - exists(NodeEx mid | fwdFlow(mid, _, config) and cc = false | - jumpStep(mid, node, config) or - additionalJumpStep(mid, node, config) or - additionalJumpStateStep(mid, _, node, _, config) - ) - or - // store - exists(NodeEx mid | - useFieldFlow(config) and - fwdFlow(mid, cc, config) and - store(mid, _, node, _, config) - ) - or - // read - exists(ContentSet c | - fwdFlowReadSet(c, node, cc, config) and - fwdFlowConsCandSet(c, _, config) - ) - or - // flow into a callable - fwdFlowIn(_, _, _, node, config) and - cc = true - or - // flow out of a callable - fwdFlowOut(_, node, false, config) and - cc = false - or - // flow through a callable - exists(DataFlowCall call | - fwdFlowOutFromArg(call, node, config) and - fwdFlowIsEntered(call, cc, config) - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowIn( - DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p, Configuration config - ) { - // call context cannot help reduce virtual dispatch - fwdFlow(arg, cc, config) and - viableParamArgEx(call, p, arg) and - not fullBarrier(p, config) and - ( - cc = false - or - cc = true and - not reducedViableImplInCallContext(call, _, _) - ) - or - // call context may help reduce virtual dispatch - exists(DataFlowCallable target | - fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target, config) and - target = viableImplInSomeFwdFlowCallContextExt(call, config) and - cc = true - ) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - fwdFlowIn(call, _, cc, _, config) - } - - pragma[nomagic] - private predicate fwdFlowInReducedViableImplInSomeCallContext( - DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target, Configuration config - ) { - fwdFlow(arg, true, config) and - viableParamArgEx(call, p, arg) and - reducedViableImplInCallContext(call, _, _) and - target = p.getEnclosingCallable() and - not fullBarrier(p, config) - } - - /** - * Gets a viable dispatch target of `call` in the context `ctx`. This is - * restricted to those `call`s for which a context might make a difference, - * and to `ctx`s that are reachable in `fwdFlow`. - */ - pragma[nomagic] - private DataFlowCallable viableImplInSomeFwdFlowCallContextExt( - DataFlowCall call, Configuration config - ) { - exists(DataFlowCall ctx | - fwdFlowIsEntered(ctx, _, config) and - result = viableImplInCallContextExt(call, ctx) - ) - } - - private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) } - - pragma[nomagic] - private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc, Configuration config) { - exists(NodeEx mid | - fwdFlow(mid, cc, config) and - readSet(mid, c, node, config) - ) - } - - /** - * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - fwdFlow(mid, _, config) and - store(mid, tc, node, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `cs` may be interpreted in a read as the target of some store - * into `c`, in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCandSet(ContentSet cs, Content c, Configuration config) { - fwdFlowConsCand(c, config) and - c = cs.getAReadContent() - } - - pragma[nomagic] - private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { - exists(RetNodeEx ret | - fwdFlow(ret, cc, config) and - ret.getReturnPosition() = pos - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) { - exists(ReturnPosition pos | - fwdFlowReturnPosition(pos, cc, config) and - viableReturnPosOutEx(call, pos, out) and - not fullBarrier(out, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) { - fwdFlowOut(call, out, true, config) - } - - private predicate stateStepFwd(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1 | - additionalLocalStateStep(node1, state1, _, state2, config) or - additionalJumpStateStep(node1, state1, _, state2, config) - | - fwdFlow(node1, config) - ) - } - - private predicate fwdFlowState(FlowState state, Configuration config) { - sourceNode(_, state, config) - or - exists(FlowState state0 | - fwdFlowState(state0, config) and - stateStepFwd(state0, state, config) - ) - } - - /** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink. - */ - pragma[nomagic] - private predicate revFlow(NodeEx node, boolean toReturn, Configuration config) { - revFlow0(node, toReturn, config) and - fwdFlow(node, config) - } - - pragma[nomagic] - private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) { - exists(FlowState state | - fwdFlow(node, pragma[only_bind_into](config)) and - sinkNode(node, state, config) and - fwdFlowState(state, pragma[only_bind_into](config)) and - if hasSinkCallCtx(config) then toReturn = true else toReturn = false - ) - or - exists(NodeEx mid | revFlow(mid, toReturn, config) | - localFlowStep(node, mid, config) or - additionalLocalFlowStep(node, mid, config) or - additionalLocalStateStep(node, _, mid, _, config) - ) - or - exists(NodeEx mid | revFlow(mid, _, config) and toReturn = false | - jumpStep(node, mid, config) or - additionalJumpStep(node, mid, config) or - additionalJumpStateStep(node, _, mid, _, config) - ) - or - // store - exists(Content c | - revFlowStore(c, node, toReturn, config) and - revFlowConsCand(c, config) - ) - or - // read - exists(NodeEx mid, ContentSet c | - readSet(node, c, mid, config) and - fwdFlowConsCandSet(c, _, pragma[only_bind_into](config)) and - revFlow(mid, toReturn, pragma[only_bind_into](config)) - ) - or - // flow into a callable - revFlowIn(_, node, false, config) and - toReturn = false - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(pos, config) and - node.(RetNodeEx).getReturnPosition() = pos and - toReturn = true - ) - or - // flow through a callable - exists(DataFlowCall call | - revFlowInToReturn(call, node, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - /** - * Holds if `c` is the target of a read in the flow covered by `revFlow`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, ContentSet cs | - fwdFlow(node, pragma[only_bind_into](config)) and - readSet(node, cs, mid, config) and - fwdFlowConsCandSet(cs, c, pragma[only_bind_into](config)) and - revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) { - exists(NodeEx mid, TypedContent tc | - revFlow(mid, toReturn, pragma[only_bind_into](config)) and - fwdFlowConsCand(c, pragma[only_bind_into](config)) and - store(node, tc, mid, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `c` is the target of both a read and a store in the flow covered - * by `revFlow`. - */ - pragma[nomagic] - additional predicate revFlowIsReadAndStored(Content c, Configuration conf) { - revFlowConsCand(c, conf) and - revFlowStore(c, _, _, conf) - } - - pragma[nomagic] - additional predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config - ) { - fwdFlowReturnPosition(pos, _, config) and - viableReturnPosOutEx(call, pos, out) - } - - pragma[nomagic] - private predicate revFlowOut(ReturnPosition pos, Configuration config) { - exists(NodeEx out | - revFlow(out, _, config) and - viableReturnPosOutNodeCandFwd1(_, pos, out, config) - ) - } - - pragma[nomagic] - additional predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config - ) { - fwdFlowIn(call, arg, _, p, config) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate revFlowIn( - DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config - ) { - exists(ParamNodeEx p | - revFlow(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) { - revFlowIn(call, arg, true, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(NodeEx out | - revFlow(out, toReturn, config) and - fwdFlowOutFromArg(call, out, config) - ) - } - - private predicate stateStepRev(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - additionalLocalStateStep(node1, state1, node2, state2, config) or - additionalJumpStateStep(node1, state1, node2, state2, config) - | - revFlow(node1, _, pragma[only_bind_into](config)) and - revFlow(node2, _, pragma[only_bind_into](config)) and - fwdFlowState(state1, pragma[only_bind_into](config)) and - fwdFlowState(state2, pragma[only_bind_into](config)) - ) - } - - additional predicate revFlowState(FlowState state, Configuration config) { - exists(NodeEx node | - sinkNode(node, state, config) and - revFlow(node, _, pragma[only_bind_into](config)) and - fwdFlowState(state, pragma[only_bind_into](config)) - ) - or - exists(FlowState state0 | - revFlowState(state0, config) and - stateStepRev(state, state0, config) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Content c | - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - revFlow(node2, pragma[only_bind_into](config)) and - store(node1, tc, node2, contentType, config) and - c = tc.getContent() and - exists(ap1) - ) - } - - pragma[nomagic] - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) { - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - read(n1, c, n2, pragma[only_bind_into](config)) and - revFlow(n2, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, config) and - exists(ap) - } - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, _, pragma[only_bind_into](config)) and - exists(state) and - exists(ap) - } - - private predicate throughFlowNodeCand(NodeEx node, Configuration config) { - revFlow(node, true, config) and - fwdFlow(node, true, config) and - not inBarrier(node, config) and - not outBarrier(node, config) - } - - /** Holds if flow may return from `callable`. */ - pragma[nomagic] - private predicate returnFlowCallableNodeCand( - DataFlowCallable callable, ReturnKindExt kind, Configuration config - ) { - exists(RetNodeEx ret | - throughFlowNodeCand(ret, config) and - callable = ret.getEnclosingCallable() and - kind = ret.getKind() - ) - } - - /** - * Holds if flow may enter through `p` and reach a return node making `p` a - * candidate for the origin of a summary. - */ - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(DataFlowCallable c, ReturnKindExt kind | - throughFlowNodeCand(p, config) and - returnFlowCallableNodeCand(c, kind, config) and - p.getEnclosingCallable() = c and - exists(ap) and - parameterFlowThroughAllowed(p, kind) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - throughFlowNodeCand(ret, config) and - kind = ret.getKind() and - exists(argAp) and - exists(ap) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, boolean toReturn | - revFlow(arg, toReturn, config) and - revFlowInToReturn(call, arg, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, config)) and - fields = count(Content f0 | fwdFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | fwdFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, config)) and - fields = count(Content f0 | revFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | revFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | revFlow(n, b, config)) - } - /* End: Stage 1 logic. */ -} - -pragma[noinline] -private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - localFlowStep(node1, node2, config) -} - -pragma[noinline] -private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - additionalLocalFlowStep(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCand1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config -) { - Stage1::revFlow(out, config) and - Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, Configuration config -) { - exists(ReturnPosition pos | - viableReturnPosOutNodeCand1(call, pos, out, config) and - pos = ret.getReturnPosition() and - kind = pos.getKind() and - Stage1::revFlow(ret, config) and - not outBarrier(ret, config) and - not inBarrier(out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCand1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config -) { - Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and - Stage1::revFlow(arg, config) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config -) { - viableParamArgNodeCand1(call, p, arg, config) and - Stage1::revFlow(p, config) and - not outBarrier(arg, config) and - not inBarrier(p, config) -} - -/** - * Gets the amount of forward branching on the origin of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int branch(NodeEx n1, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n1, _, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) - ) -} - -/** - * Gets the amount of backward branching on the target of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int join(NodeEx n2, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n, _, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) - ) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. The - * `allowsFieldFlow` flag indicates whether the branching is within the limit - * specified by the configuration. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, ret, kind, out, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(ret, pragma[only_bind_into](config)) and - j = join(out, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. The `allowsFieldFlow` flag indicates whether - * the branching is within the limit specified by the configuration. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config -) { - flowIntoCallNodeCand1(call, arg, p, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(arg, pragma[only_bind_into](config)) and - j = join(p, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -private signature module StageSig { - class Ap; - - predicate revFlow(NodeEx node, Configuration config); - - predicate revFlowAp(NodeEx node, Ap ap, Configuration config); - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config); - - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config); - - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config); - - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ); - - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ); - - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config); -} - -private module MkStage { - class ApApprox = PrevStage::Ap; - - signature module StageParam { - class Ap; - - class ApNil extends Ap; - - bindingset[result, ap] - ApApprox getApprox(Ap ap); - - ApNil getApNil(NodeEx node); - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail); - - /** - * An approximation of `Content` that corresponds to the precision level of - * `Ap`, such that the mappings from both `Ap` and `Content` to this type - * are functional. - */ - class ApHeadContent; - - ApHeadContent getHeadContent(Ap ap); - - ApHeadContent projectToHeadContent(Content c); - - class ApOption; - - ApOption apNone(); - - ApOption apSome(Ap ap); - - class Cc; - - class CcCall extends Cc; - - // TODO: member predicate on CcCall - predicate matchesCall(CcCall cc, DataFlowCall call); - - class CcNoCall extends Cc; - - Cc ccNone(); - - CcCall ccSomeCall(); - - class LocalCc; - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc); - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ); - - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config - ); - - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config - ); - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config); - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType); - } - - module Stage implements StageSig { - import Param - - /* Begin: Stage logic. */ - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - private predicate revFlowApAlias(NodeEx node, ApApprox apa, Configuration config) { - PrevStage::revFlowAp(node, apa, config) - } - - pragma[nomagic] - private predicate flowIntoCallApa( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa, - Configuration config - ) { - flowIntoCall(call, arg, p, allowsFieldFlow, config) and - PrevStage::revFlowAp(p, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(arg, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowOutOfCallApa( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - ApApprox apa, Configuration config - ) { - flowOutOfCall(call, ret, kind, out, allowsFieldFlow, config) and - PrevStage::revFlowAp(out, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(ret, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowThroughOutOfCall( - DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, - ApApprox argApa, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa, pragma[only_bind_into](config)) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) and - matchesCall(ccc, call) - ) - } - - /** - * Holds if `node` is reachable with access path `ap` from a source in the - * configuration `config`. - * - * The call context `cc` records whether the node is reached through an - * argument in a call, and if so, `summaryCtx` and `argAp` record the - * corresponding parameter position and access path of that argument, respectively. - */ - pragma[nomagic] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa, config) and - PrevStage::revFlow(node, state, apa, config) and - filter(node, state, ap, config) - } - - pragma[inline] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, cc, summaryCtx, argAp, ap, _, config) - } - - pragma[nomagic] - private predicate fwdFlow0( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - sourceNode(node, state, config) and - (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and - argAp = apNone() and - summaryCtx = TParamNodeNone() and - ap = getApNil(node) and - apa = getApprox(ap) - or - exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | - fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0, config) and - localCc = getLocalCc(mid, cc) - | - localStep(mid, state0, node, state, true, _, config, localCc) and - ap = ap0 and - apa = apa0 - or - localStep(mid, state0, node, state, false, ap, config, localCc) and - ap0 instanceof ApNil and - apa = getApprox(ap) - ) - or - exists(NodeEx mid | - fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa, pragma[only_bind_into](config)) and - jumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(mid, state, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(mid, state0, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStateStep(mid, state0, node, state, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - // store - exists(TypedContent tc, Ap ap0 | - fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp, config) and - ap = apCons(tc, ap0) and - apa = getApprox(ap) - ) - or - // read - exists(Ap ap0, Content c | - fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp, config) and - fwdFlowConsCand(ap0, c, ap, config) and - apa = getApprox(ap) - ) - or - // flow into a callable - fwdFlowIn(_, node, state, _, cc, _, _, ap, apa, config) and - if PrevStage::parameterMayFlowThrough(node, apa, config) - then ( - summaryCtx = TParamNodeSome(node.asNode()) and - argAp = apSome(ap) - ) else ( - summaryCtx = TParamNodeNone() and argAp = apNone() - ) - or - // flow out of a callable - exists( - DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, - DataFlowCallable inner - | - fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa, config) and - flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa, config) and - inner = ret.getEnclosingCallable() and - cc = getCallContextReturn(inner, call, innercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - or - // flow through a callable - exists( - DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, ApApprox innerArgApa - | - fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowStore( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(DataFlowType contentType, ApApprox apa1 | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1, config) and - PrevStage::storeStepCand(node1, apa1, tc, node2, contentType, config) and - typecheckStore(ap1, contentType) - ) - } - - /** - * Holds if forward flow with access path `tail` reaches a store of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(TypedContent tc | - fwdFlowStore(_, tail, tc, _, _, _, _, _, config) and - tc.getContent() = c and - cons = apCons(tc, tail) - ) - } - - pragma[nomagic] - private predicate readStepCand( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - PrevStage::readStepCand(node1, c, node2, config) and - apc = projectToHeadContent(c) - } - - bindingset[node1, apc] - pragma[inline_late] - private predicate readStepCand0( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - readStepCand(node1, apc, c, node2, config) - } - - pragma[nomagic] - private predicate fwdFlowRead( - Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(ApHeadContent apc | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap, config) and - apc = getHeadContent(ap) and - readStepCand0(node1, apc, c, node2, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowIn( - DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, - ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa, Configuration config - ) { - exists(ArgNodeEx arg, boolean allowsFieldFlow | - fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa, config) and - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowRetFromArg( - RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, ApApprox argApa, - Ap ap, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - fwdFlow(pragma[only_bind_into](ret), state, ccc, - TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), - pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa), - pragma[only_bind_into](config)) and - kind = ret.getKind() and - parameterFlowThroughAllowed(summaryCtx, kind) and - argApa = getApprox(argAp) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) - ) - } - - pragma[inline] - private predicate fwdFlowThrough0( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, - Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa, config) and - fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp, config) - } - - pragma[nomagic] - private predicate fwdFlowThrough( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa, - config) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` - * and data might flow through the target callable and back out at `call`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered( - DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, - ParamNodeEx p, Ap ap, Configuration config - ) { - exists(ApApprox apa | - fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(p, apa, config) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate storeStepFwd( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config - ) { - fwdFlowStore(node1, ap1, tc, node2, _, _, _, _, config) and - ap2 = apCons(tc, ap1) and - fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _, config) - } - - private predicate readStepFwd( - NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config - ) { - fwdFlowRead(ap1, c, n1, n2, _, _, _, _, config) and - fwdFlowConsCand(ap1, c, ap2, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough0( - DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, - ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, - innerArgApa, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough( - RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, - Ap ap, Configuration config - ) { - exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | - returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa, config) and - pos = ret.getReturnPosition() and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowThroughIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap, - Configuration config - ) { - exists(ApApprox argApa | - flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), - allowsFieldFlow, argApa, pragma[only_bind_into](config)) and - fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa, - pragma[only_bind_into](config)) and - returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap, - pragma[only_bind_into](config)) and - if allowsFieldFlow = false then argAp instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowIntoCallAp( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap, - Configuration config - ) { - exists(ApApprox apa | - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - fwdFlow(arg, _, _, _, _, ap, apa, config) - ) - } - - pragma[nomagic] - private predicate flowOutOfCallAp( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, - Ap ap, Configuration config - ) { - exists(ApApprox apa | - flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa, config) and - fwdFlow(ret, _, _, _, _, ap, apa, config) and - pos = ret.getReturnPosition() - ) - } - - /** - * Holds if `node` with access path `ap` is part of a path from a source to a - * sink in the configuration `config`. - * - * The parameter `returnCtx` records whether (and how) the node must be returned - * from the enclosing callable in order to reach a sink, and if so, `returnAp` - * records the access path of the returned value. - */ - pragma[nomagic] - additional predicate revFlow( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - revFlow0(node, state, returnCtx, returnAp, ap, config) and - fwdFlow(node, state, _, _, _, ap, config) - } - - pragma[nomagic] - private predicate revFlow0( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, _, _, _, ap, config) and - sinkNode(node, state, config) and - ( - if hasSinkCallCtx(config) - then returnCtx = TReturnCtxNoFlowThrough() - else returnCtx = TReturnCtxNone() - ) and - returnAp = apNone() and - ap instanceof ApNil - or - exists(NodeEx mid, FlowState state0 | - localStep(node, state, mid, state0, true, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, ap, config) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap, pragma[only_bind_into](config)) and - localStep(node, pragma[only_bind_into](state), mid, state0, false, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, nil, pragma[only_bind_into](config)) and - ap instanceof ApNil - ) - or - exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, state, _, _, ap, config) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStep(node, mid, config) and - revFlow(pragma[only_bind_into](mid), state, _, _, nil, pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStateStep(node, state, mid, state0, config) and - revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil, - pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - // store - exists(Ap ap0, Content c | - revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp, config) and - revFlowConsCand(ap0, c, ap, config) - ) - or - // read - exists(NodeEx mid, Ap ap0 | - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - readStepFwd(node, ap, _, mid, ap0, config) - ) - or - // flow into a callable - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, state, TReturnCtxNone(), returnAp, ap, config) and - flowIntoCallAp(_, node, p, allowsFieldFlow, ap, config) and - (if allowsFieldFlow = false then ap instanceof ApNil else any()) and - returnCtx = TReturnCtxNone() - ) - or - // flow through a callable - exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, node, p, _, ap, innerReturnAp, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(_, node, pos, state, _, _, ap, config) and - if returnFlowsThrough(node, pos, state, _, _, _, ap, config) - then ( - returnCtx = TReturnCtxMaybeFlowThrough(pos) and - returnAp = apSome(ap) - ) else ( - returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() - ) - ) - } - - pragma[nomagic] - private predicate revFlowStore( - Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, - ReturnCtx returnCtx, ApOption returnAp, Configuration config - ) { - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - storeStepFwd(node, ap, tc, mid, ap0, config) and - tc.getContent() = c - } - - /** - * Holds if reverse flow with access path `tail` reaches a read of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(NodeEx mid, Ap tail0 | - revFlow(mid, _, _, _, tail, config) and - tail = pragma[only_bind_into](tail0) and - readStepFwd(_, cons, c, mid, tail0, config) - ) - } - - pragma[nomagic] - private predicate revFlowOut( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, - ApOption returnAp, Ap ap, Configuration config - ) { - exists(NodeEx out, boolean allowsFieldFlow | - revFlow(out, state, returnCtx, returnAp, ap, config) and - flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate revFlowParamToReturn( - ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap, Configuration config - ) { - revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), - pragma[only_bind_into](ap), pragma[only_bind_into](config)) and - parameterFlowThroughAllowed(p, pos.getKind()) and - PrevStage::parameterMayFlowThrough(p, getApprox(ap), config) - } - - pragma[nomagic] - private predicate revFlowThrough( - DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, - ApOption returnAp, Ap ap, Ap innerReturnAp, Configuration config - ) { - revFlowParamToReturn(p, state, pos, innerReturnAp, ap, config) and - revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned( - DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap, - Configuration config - ) { - exists(RetNodeEx ret, FlowState state, CcCall ccc | - revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap, config) and - returnFlowsThrough(ret, pos, state, ccc, _, _, ap, config) and - matchesCall(ccc, call) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Ap ap2, Content c | - PrevStage::storeStepCand(node1, _, tc, node2, contentType, config) and - revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _, config) and - revFlowConsCand(ap2, c, ap1, config) - ) - } - - predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(Ap ap1, Ap ap2 | - revFlow(node2, _, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and - readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _, - pragma[only_bind_into](config)) - ) - } - - additional predicate revFlow(NodeEx node, FlowState state, Configuration config) { - revFlow(node, state, _, _, _, config) - } - - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, _, _, ap, config) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, _, _, _, ap, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, Configuration config) { - revFlow(node, _, _, _, _, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, ap, config) - } - - private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepFwd(_, ap, tc, _, _, config) - } - - private predicate revConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepCand(_, ap, tc, _, _, config) - } - - private predicate validAp(Ap ap, Configuration config) { - revFlow(_, _, _, _, ap, config) and ap instanceof ApNil - or - exists(TypedContent head, Ap tail | - consCand(head, tail, config) and - ap = apCons(head, tail) - ) - } - - additional predicate consCand(TypedContent tc, Ap ap, Configuration config) { - revConsCand(tc, ap, config) and - validAp(ap, config) - } - - pragma[nomagic] - private predicate parameterFlowsThroughRev( - ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp, Configuration config - ) { - revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap, config) and - parameterFlowThroughAllowed(p, pos.getKind()) - } - - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(ReturnPosition pos | - returnFlowsThrough(_, pos, _, _, p, ap, _, config) and - parameterFlowsThroughRev(p, ap, pos, _, config) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - exists(ParamNodeEx p, ReturnPosition pos | - returnFlowsThrough(ret, pos, _, _, p, argAp, ap, config) and - parameterFlowsThroughRev(p, argAp, pos, ap, config) and - kind = pos.getKind() - ) - } - - pragma[nomagic] - private predicate revFlowThroughArg( - DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, - Ap ap, Configuration config - ) { - exists(ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp, config) - ) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | - revFlow(arg, state, returnCtx, returnAp, ap, config) and - revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, config)) and - fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and - states = count(FlowState state | fwdFlow(_, state, _, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | - fwdFlow(n, state, cc, summaryCtx, argAp, ap, config) - ) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, _, _, _, config)) and - fields = count(TypedContent f0 | consCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and - states = count(FlowState state | revFlow(_, state, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | - revFlow(n, state, returnCtx, retAp, ap, config) - ) - } - /* End: Stage logic. */ - } -} - -private module BooleanCallContext { - class Cc extends boolean { - Cc() { this in [true, false] } - } - - class CcCall extends Cc { - CcCall() { this = true } - } - - /** Holds if the call context may be `call`. */ - predicate matchesCall(CcCall cc, DataFlowCall call) { any() } - - class CcNoCall extends Cc { - CcNoCall() { this = false } - } - - Cc ccNone() { result = false } - - CcCall ccSomeCall() { result = true } - - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } -} - -private module Level1CallContext { - class Cc = CallContext; - - class CcCall = CallContextCall; - - pragma[inline] - predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } - - class CcNoCall = CallContextNoCall; - - Cc ccNone() { result instanceof CallContextAny } - - CcCall ccSomeCall() { result instanceof CallContextSomeCall } - - module NoLocalCallContext { - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSiteDispatch(call, c) - then result = TSpecificCall(call) - else result = TSomeCall() - } - } - - module LocalCallContext { - class LocalCc = LocalCallContext; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { - result = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - node.getEnclosingCallable()) - } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() - } - } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { - checkCallContextReturn(innercc, c, call) and - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() - } -} - -private module Stage2Param implements MkStage::StageParam { - private module PrevStage = Stage1; - - class Ap extends boolean { - Ap() { this in [true, false] } - } - - class ApNil extends Ap { - ApNil() { this = false } - } - - bindingset[result, ap] - PrevStage::Ap getApprox(Ap ap) { any() } - - ApNil getApNil(NodeEx node) { Stage1::revFlow(node, _) and exists(result) } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } - - class ApHeadContent = Unit; - - pragma[inline] - ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } - - ApHeadContent projectToHeadContent(Content c) { any() } - - class ApOption = BooleanOption; - - ApOption apNone() { result = TBooleanNone() } - - ApOption apSome(Ap ap) { result = TBooleanSome(ap) } - - import Level1CallContext - import NoLocalCallContext - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - ( - preservesValue = true and - localFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalStateStep(node1, state1, node2, state2, config) - ) and - exists(ap) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand1/6; - - predicate flowIntoCall = flowIntoCallNodeCand1/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - expectsContentEx(node, c) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - PrevStage::revFlowState(state, pragma[only_bind_into](config)) and - exists(ap) and - not stateBarrier(node, state, config) and - ( - notExpectsContent(node) - or - ap = true and - expectsContentCand(node, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage2 implements StageSig { - import MkStage::Stage -} - -pragma[nomagic] -private predicate flowOutOfCallNodeCand2( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -pragma[nomagic] -private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -private module LocalFlowBigStep { - /** - * A node where some checking is required, and hence the big-step relation - * is not allowed to step over. - */ - private class FlowCheckNode extends NodeEx { - FlowCheckNode() { - castNode(this.asNode()) or - clearsContentCached(this.asNode(), _) or - expectsContentCached(this.asNode(), _) - } - } - - /** - * Holds if `node` can be the first node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowEntry(NodeEx node, FlowState state, Configuration config) { - Stage2::revFlow(node, state, config) and - ( - sourceNode(node, state, config) - or - jumpStep(_, node, config) - or - additionalJumpStep(_, node, config) - or - additionalJumpStateStep(_, _, node, state, config) - or - node instanceof ParamNodeEx - or - node.asNode() instanceof OutNodeExt - or - Stage2::storeStepCand(_, _, _, node, _, config) - or - Stage2::readStepCand(_, _, node, config) - or - node instanceof FlowCheckNode - or - exists(FlowState s | - additionalLocalStateStep(_, s, node, state, config) and - s != state - ) - ) - } - - /** - * Holds if `node` can be the last node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowExit(NodeEx node, FlowState state, Configuration config) { - exists(NodeEx next | Stage2::revFlow(next, state, config) | - jumpStep(node, next, config) or - additionalJumpStep(node, next, config) or - flowIntoCallNodeCand2(_, node, next, _, config) or - flowOutOfCallNodeCand2(_, node, _, next, _, config) or - Stage2::storeStepCand(node, _, _, next, _, config) or - Stage2::readStepCand(node, _, next, config) - ) - or - exists(NodeEx next, FlowState s | Stage2::revFlow(next, s, config) | - additionalJumpStateStep(node, state, next, s, config) - or - additionalLocalStateStep(node, state, next, s, config) and - s != state - ) - or - Stage2::revFlow(node, state, config) and - node instanceof FlowCheckNode - or - sinkNode(node, state, config) - } - - pragma[noinline] - private predicate additionalLocalFlowStepNodeCand2( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, Configuration config - ) { - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 and - Stage2::revFlow(node1, pragma[only_bind_into](state1), false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false, - pragma[only_bind_into](config)) - or - additionalLocalStateStep(node1, state1, node2, state2, config) and - Stage2::revFlow(node1, state1, false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, state2, false, pragma[only_bind_into](config)) - } - - /** - * Holds if the local path from `node1` to `node2` is a prefix of a maximal - * subsequence of local flow steps in a dataflow path. - * - * This is the transitive closure of `[additional]localFlowStep` beginning - * at `localFlowEntry`. - */ - pragma[nomagic] - private predicate localFlowStepPlus( - NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, - Configuration config, LocalCallContext cc - ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and - ( - localFlowEntry(node1, pragma[only_bind_into](state), pragma[only_bind_into](config)) and - ( - localFlowStepNodeCand1(node1, node2, config) and - preservesValue = true and - t = node1.getDataFlowType() and // irrelevant dummy value - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - or - additionalLocalFlowStepNodeCand2(node1, state, node2, state, config) and - preservesValue = false and - t = node2.getDataFlowType() - ) and - node1 != node2 and - cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) - or - exists(NodeEx mid | - localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, - pragma[only_bind_into](config), cc) and - localFlowStepNodeCand1(mid, node2, config) and - not mid instanceof FlowCheckNode and - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - ) - or - exists(NodeEx mid | - localFlowStepPlus(node1, state, mid, _, _, pragma[only_bind_into](config), cc) and - additionalLocalFlowStepNodeCand2(mid, state, node2, state, config) and - not mid instanceof FlowCheckNode and - preservesValue = false and - t = node2.getDataFlowType() - ) - ) - } - - /** - * Holds if `node1` can step to `node2` in one or more local steps and this - * path can occur as a maximal subsequence of local steps in a dataflow path. - */ - pragma[nomagic] - predicate localFlowBigStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - DataFlowType t, Configuration config, LocalCallContext callContext - ) { - localFlowStepPlus(node1, state1, node2, preservesValue, t, config, callContext) and - localFlowExit(node2, state1, config) and - state1 = state2 - or - additionalLocalFlowStepNodeCand2(node1, state1, node2, state2, config) and - state1 != state2 and - preservesValue = false and - t = node2.getDataFlowType() and - callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) - } -} - -private import LocalFlowBigStep - -private module Stage3Param implements MkStage::StageParam { - private module PrevStage = Stage2; - - class Ap = ApproxAccessPathFront; - - class ApNil = ApproxAccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } - - class ApHeadContent = ContentApprox; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - predicate projectToHeadContent = getContentApprox/1; - - class ApOption = ApproxAccessPathFrontOption; - - ApOption apNone() { result = TApproxAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } - - import BooleanCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApproxAccessPathFrontNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand2/6; - - predicate flowIntoCall = flowIntoCallNodeCand2/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getAHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage3 implements StageSig { - import MkStage::Stage -} - -private module Stage4Param implements MkStage::StageParam { - private module PrevStage = Stage3; - - class Ap = AccessPathFront; - - class ApNil = AccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathFrontOption; - - ApOption apNone() { result = TAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } - - import BooleanCallContext - - pragma[nomagic] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) and - exists(lcc) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clearSet(NodeEx node, ContentSet c, Configuration config) { - PrevStage::revFlow(node, config) and - clearsContentCached(node.asNode(), c) - } - - pragma[nomagic] - private predicate clearContent(NodeEx node, Content c, Configuration config) { - exists(ContentSet cs | - PrevStage::readStepCand(_, pragma[only_bind_into](c), _, pragma[only_bind_into](config)) and - c = cs.getAReadContent() and - clearSet(node, cs, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clear(NodeEx node, Ap ap, Configuration config) { - clearContent(node, ap.getHead().getContent(), config) - } - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - not clear(node, ap, config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage4 implements StageSig { - import MkStage::Stage -} - -/** - * Holds if `argApf` is recorded as the summary context for flow reaching `node` - * and remains relevant for the following pruning stage. - */ -private predicate flowCandSummaryCtx( - NodeEx node, FlowState state, AccessPathFront argApf, Configuration config -) { - exists(AccessPathFront apf | - Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf, config) and - Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf, - config) - ) -} - -/** - * Holds if a length 2 access path approximation with the head `tc` is expected - * to be expensive. - */ -private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { - exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf, config)) and - nodes = - strictcount(NodeEx n, FlowState state | - Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - or - flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - ) and - accessPathApproxCostLimits(apLimit, tupleLimit) and - apLimit < tails and - tupleLimit < (tails - 1) * nodes and - not tc.forceHighPrecision() - ) -} - -private newtype TAccessPathApprox = - TNil(DataFlowType t) or - TConsNil(TypedContent tc, DataFlowType t) { - Stage4::consCand(tc, TFrontNil(t), _) and - not expensiveLen2unfolding(tc, _) - } or - TConsCons(TypedContent tc1, TypedContent tc2, int len) { - Stage4::consCand(tc1, TFrontHead(tc2), _) and - len in [2 .. accessPathLimit()] and - not expensiveLen2unfolding(tc1, _) - } or - TCons1(TypedContent tc, int len) { - len in [1 .. accessPathLimit()] and - expensiveLen2unfolding(tc, _) - } - -/** - * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only - * the first two elements of the list and its length are tracked. If data flows - * from a source to a given node with a given `AccessPathApprox`, this indicates - * the sequence of dereference operations needed to get from the value in the node - * to the tracked object. The final type indicates the type of the tracked object. - */ -abstract private class AccessPathApprox extends TAccessPathApprox { - abstract string toString(); - - abstract TypedContent getHead(); - - abstract int len(); - - abstract DataFlowType getType(); - - abstract AccessPathFront getFront(); - - /** Gets the access path obtained by popping `head` from this path, if any. */ - abstract AccessPathApprox pop(TypedContent head); -} - -private class AccessPathApproxNil extends AccessPathApprox, TNil { - private DataFlowType t; - - AccessPathApproxNil() { this = TNil(t) } - - override string toString() { result = concat(": " + ppReprType(t)) } - - override TypedContent getHead() { none() } - - override int len() { result = 0 } - - override DataFlowType getType() { result = t } - - override AccessPathFront getFront() { result = TFrontNil(t) } - - override AccessPathApprox pop(TypedContent head) { none() } -} - -abstract private class AccessPathApproxCons extends AccessPathApprox { } - -private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { - private TypedContent tc; - private DataFlowType t; - - AccessPathApproxConsNil() { this = TConsNil(tc, t) } - - override string toString() { - // The `concat` becomes "" if `ppReprType` has no result. - result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) - } - - override TypedContent getHead() { result = tc } - - override int len() { result = 1 } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } -} - -private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { - private TypedContent tc1; - private TypedContent tc2; - private int len; - - AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - - override string toString() { - if len = 2 - then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" - else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc1 } - - override int len() { result = len } - - override DataFlowType getType() { result = tc1.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc1) } - - override AccessPathApprox pop(TypedContent head) { - head = tc1 and - ( - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - } -} - -private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { - private TypedContent tc; - private int len; - - AccessPathApproxCons1() { this = TCons1(tc, len) } - - override string toString() { - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc } - - override int len() { result = len } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { - head = tc and - ( - exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2), _) | - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - or - exists(DataFlowType t | - len = 1 and - Stage4::consCand(tc, TFrontNil(t), _) and - result = TNil(t) - ) - ) - } -} - -/** Gets the access path obtained by popping `tc` from `ap`, if any. */ -private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } - -/** Gets the access path obtained by pushing `tc` onto `ap`. */ -private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } - -private newtype TAccessPathApproxOption = - TAccessPathApproxNone() or - TAccessPathApproxSome(AccessPathApprox apa) - -private class AccessPathApproxOption extends TAccessPathApproxOption { - string toString() { - this = TAccessPathApproxNone() and result = "" - or - this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) - } -} - -private module Stage5Param implements MkStage::StageParam { - private module PrevStage = Stage4; - - class Ap = AccessPathApprox; - - class ApNil = AccessPathApproxNil; - - pragma[nomagic] - PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathApproxOption; - - ApOption apNone() { result = TAccessPathApproxNone() } - - ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } - - import Level1CallContext - import LocalCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, lcc) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { any() } - - // Type checking is not necessary here as it has already been done in stage 3. - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage5 = MkStage::Stage; - -bindingset[conf, result] -private Configuration unbindConf(Configuration conf) { - exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c)) -} - -pragma[nomagic] -private predicate nodeMayUseSummary0( - NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(AccessPathApprox apa0 | - Stage5::parameterMayFlowThrough(p, _, _) and - Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0, config) and - Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), - TAccessPathApproxSome(apa), apa0, config) - ) -} - -pragma[nomagic] -private predicate nodeMayUseSummary( - NodeEx n, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::parameterMayFlowThrough(p, apa, config) and - nodeMayUseSummary0(n, p, state, apa, config) - ) -} - -private newtype TSummaryCtx = - TSummaryCtxNone() or - TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { - exists(Configuration config | - Stage5::parameterMayFlowThrough(p, ap.getApprox(), config) and - Stage5::revFlow(p, state, _, config) - ) - } - -/** - * A context for generating flow summaries. This represents flow entry through - * a specific parameter with an access path of a specific shape. - * - * Summaries are only created for parameters that may flow through. - */ -abstract private class SummaryCtx extends TSummaryCtx { - abstract string toString(); -} - -/** A summary context from which no flow summary can be generated. */ -private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { - override string toString() { result = "" } -} - -/** A summary context from which a flow summary can be generated. */ -private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParamNodeEx p; - private FlowState s; - private AccessPath ap; - - SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } - - ParameterPosition getParameterPos() { p.isParameterOf(_, result) } - - ParamNodeEx getParamNode() { result = p } - - override string toString() { result = p + ": " + ap } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** - * Gets the number of length 2 access path approximations that correspond to `apa`. - */ -private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { - exists(TypedContent tc, int len | - tc = apa.getHead() and - len = apa.len() and - result = - strictcount(AccessPathFront apf | - Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), - config) - ) - ) -} - -private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = - strictcount(NodeEx n, FlowState state | - Stage5::revFlow(n, state, apa, config) or nodeMayUseSummary(n, state, apa, config) - ) -} - -/** - * Holds if a length 2 access path approximation matching `apa` is expected - * to be expensive. - */ -private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = count1to2unfold(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - apLimit < aps and - tupleLimit < (aps - 1) * nodes - ) -} - -private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { - exists(TypedContent head | - apa.pop(head) = result and - Stage5::consCand(head, result, config) - ) -} - -/** - * Holds with `unfold = false` if a precise head-tail representation of `apa` is - * expected to be expensive. Holds with `unfold = true` otherwise. - */ -private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { - if apa.getHead().forceHighPrecision() - then unfold = true - else - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = countPotentialAps(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true - ) -} - -/** - * Gets the number of `AccessPath`s that correspond to `apa`. - */ -pragma[assume_small_delta] -private int countAps(AccessPathApprox apa, Configuration config) { - evalUnfold(apa, false, config) and - result = 1 and - (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) - or - evalUnfold(apa, false, config) and - result = count1to2unfold(apa, config) and - not expensiveLen1to2unfolding(apa, config) - or - evalUnfold(apa, true, config) and - result = countPotentialAps(apa, config) -} - -/** - * Gets the number of `AccessPath`s that would correspond to `apa` assuming - * that it is expanded to a precise head-tail representation. - */ -language[monotonicAggregates] -pragma[assume_small_delta] -private int countPotentialAps(AccessPathApprox apa, Configuration config) { - apa instanceof AccessPathApproxNil and result = 1 - or - result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) -} - -private newtype TAccessPath = - TAccessPathNil(DataFlowType t) or - TAccessPathCons(TypedContent head, AccessPath tail) { - exists(AccessPathApproxCons apa | - not evalUnfold(apa, false, _) and - head = apa.getHead() and - tail.getApprox() = getATail(apa, _) - ) - } or - TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - not expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head1 = apa.getHead() and - head2 = getATail(apa, _).getHead() - ) - } or - TAccessPathCons1(TypedContent head, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head = apa.getHead() - ) - } - -private newtype TPathNode = - pragma[assume_small_delta] - TPathNodeMid( - NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config - ) { - // A PathNode is introduced by a source ... - Stage5::revFlow(node, state, config) and - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - or - // ... or a step from an existing PathNode to another node. - exists(PathNodeMid mid | - pathStep(mid, node, state, cc, sc, ap) and - pragma[only_bind_into](config) = mid.getConfiguration() and - Stage5::revFlow(node, state, ap.getApprox(), pragma[only_bind_into](config)) - ) - } or - TPathNodeSink(NodeEx node, FlowState state, Configuration config) { - exists(PathNodeMid sink | - sink.isAtSink() and - node = sink.getNodeEx() and - state = sink.getState() and - config = sink.getConfiguration() - ) - } or - TPathNodeSourceGroup(string sourceGroup, Configuration config) { - exists(PathNodeImpl source | - sourceGroup = source.getSourceGroup() and - config = source.getConfiguration() - ) - } or - TPathNodeSinkGroup(string sinkGroup, Configuration config) { - exists(PathNodeSink sink | - sinkGroup = sink.getSinkGroup() and - config = sink.getConfiguration() - ) - } - -/** - * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a - * source to a given node with a given `AccessPath`, this indicates the sequence - * of dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ -private class AccessPath extends TAccessPath { - /** Gets the head of this access path, if any. */ - abstract TypedContent getHead(); - - /** Gets the tail of this access path, if any. */ - abstract AccessPath getTail(); - - /** Gets the front of this access path. */ - abstract AccessPathFront getFront(); - - /** Gets the approximation of this access path. */ - abstract AccessPathApprox getApprox(); - - /** Gets the length of this access path. */ - abstract int length(); - - /** Gets a textual representation of this access path. */ - abstract string toString(); - - /** Gets the access path obtained by popping `tc` from this access path, if any. */ - final AccessPath pop(TypedContent tc) { - result = this.getTail() and - tc = this.getHead() - } - - /** Gets the access path obtained by pushing `tc` onto this access path. */ - final AccessPath push(TypedContent tc) { this = result.pop(tc) } -} - -private class AccessPathNil extends AccessPath, TAccessPathNil { - private DataFlowType t; - - AccessPathNil() { this = TAccessPathNil(t) } - - DataFlowType getType() { result = t } - - override TypedContent getHead() { none() } - - override AccessPath getTail() { none() } - - override AccessPathFrontNil getFront() { result = TFrontNil(t) } - - override AccessPathApproxNil getApprox() { result = TNil(t) } - - override int length() { result = 0 } - - override string toString() { result = concat(": " + ppReprType(t)) } -} - -private class AccessPathCons extends AccessPath, TAccessPathCons { - private TypedContent head; - private AccessPath tail; - - AccessPathCons() { this = TAccessPathCons(head, tail) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { result = tail } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - pragma[assume_small_delta] - override AccessPathApproxCons getApprox() { - result = TConsNil(head, tail.(AccessPathNil).getType()) - or - result = TConsCons(head, tail.getHead(), this.length()) - or - result = TCons1(head, this.length()) - } - - pragma[assume_small_delta] - override int length() { result = 1 + tail.length() } - - private string toStringImpl(boolean needsSuffix) { - exists(DataFlowType t | - tail = TAccessPathNil(t) and - needsSuffix = false and - result = head.toString() + "]" + concat(" : " + ppReprType(t)) - ) - or - result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) - or - exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | - result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true - or - result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false - ) - or - exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | - result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true - or - result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false - ) - } - - override string toString() { - result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" - or - result = "[" + this.toStringImpl(false) - } -} - -private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { - private TypedContent head1; - private TypedContent head2; - private int len; - - AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } - - override TypedContent getHead() { result = head1 } - - override AccessPath getTail() { - Stage5::consCand(head1, result.getApprox(), _) and - result.getHead() = head2 and - result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head1) } - - override AccessPathApproxCons getApprox() { - result = TConsCons(head1, head2, len) or - result = TCons1(head1, len) - } - - override int length() { result = len } - - override string toString() { - if len = 2 - then result = "[" + head1.toString() + ", " + head2.toString() + "]" - else - result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" - } -} - -private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { - private TypedContent head; - private int len; - - AccessPathCons1() { this = TAccessPathCons1(head, len) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { - Stage5::consCand(head, result.getApprox(), _) and result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - - override int length() { result = len } - - override string toString() { - if len = 1 - then result = "[" + head.toString() + "]" - else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" - } -} - -abstract private class PathNodeImpl extends TPathNode { - /** Gets the `FlowState` of this node. */ - abstract FlowState getState(); - - /** Gets the associated configuration. */ - abstract Configuration getConfiguration(); - - /** Holds if this node is a source. */ - abstract predicate isSource(); - - abstract PathNodeImpl getASuccessorImpl(); - - private PathNodeImpl getASuccessorIfHidden() { - this.isHidden() and - result = this.getASuccessorImpl() - } - - pragma[nomagic] - private PathNodeImpl getANonHiddenSuccessor0() { - result = this.getASuccessorIfHidden*() and - not result.isHidden() - } - - final PathNodeImpl getANonHiddenSuccessor() { - result = this.getASuccessorImpl().getANonHiddenSuccessor0() and - not this.isHidden() - } - - abstract NodeEx getNodeEx(); - - predicate isHidden() { - not this.getConfiguration().includeHiddenNodes() and - ( - hiddenNode(this.getNodeEx().asNode()) and - not this.isSource() and - not this instanceof PathNodeSink - or - this.getNodeEx() instanceof TNodeImplicitRead - ) - } - - string getSourceGroup() { - this.isSource() and - this.getConfiguration().sourceGrouping(this.getNodeEx().asNode(), result) + not singleConfiguration() and + getConfig(state1).isAdditionalFlowStep(node1, node2) and + state2 = state1 } - predicate isFlowSource() { - this.isSource() and not exists(this.getSourceGroup()) - or - this instanceof PathNodeSourceGroup + predicate allowImplicitRead(Node node, ContentSet c) { + any(Configuration config).allowImplicitRead(node, c) } - predicate isFlowSink() { - this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or - this instanceof PathNodeSinkGroup - } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } - private string ppAp() { - this instanceof PathNodeSink and result = "" - or - exists(string s | s = this.(PathNodeMid).getAp().toString() | - if s = "" then result = "" else result = " " + s - ) - } + FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } - private string ppCtx() { - this instanceof PathNodeSink and result = "" - or - result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + predicate sourceGrouping(Node source, string sourceGroup) { + any(Configuration config).sourceGrouping(source, sourceGroup) } - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + predicate sinkGrouping(Node sink, string sinkGroup) { + any(Configuration config).sinkGrouping(sink, sinkGroup) } -} - -/** Holds if `n` can reach a sink. */ -private predicate directReach(PathNodeImpl n) { - n instanceof PathNodeSink or - n instanceof PathNodeSinkGroup or - directReach(n.getANonHiddenSuccessor()) -} - -/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ -private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } -/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { - n1.getANonHiddenSuccessor() = n2 and directReach(n2) + predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() } } -private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) +private import Impl as I +import I /** * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. */ -class PathNode instanceof PathNodeImpl { - PathNode() { reach(this) } - +class PathNode instanceof I::PathNode { /** Gets a textual representation of this element. */ final string toString() { result = super.toString() } @@ -3406,1548 +358,39 @@ class PathNode instanceof PathNodeImpl { } /** Gets the underlying `Node`. */ - final Node getNode() { super.getNodeEx().projectToNode() = result } + final Node getNode() { result = super.getNode() } /** Gets the `FlowState` of this node. */ - final FlowState getState() { result = super.getState() } + final FlowState getState() { result = getState(super.getState()) } /** Gets the associated configuration. */ - final Configuration getConfiguration() { result = super.getConfiguration() } + final Configuration getConfiguration() { result = getConfig(super.getState()) } /** Gets a successor of this node, if any. */ - final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } + final PathNode getASuccessor() { result = super.getASuccessor() } /** Holds if this node is a source. */ final predicate isSource() { super.isSource() } /** Holds if this node is a grouping of source nodes. */ - final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group, _) } + final predicate isSourceGroup(string group) { super.isSourceGroup(group) } /** Holds if this node is a grouping of sink nodes. */ - final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group, _) } + final predicate isSinkGroup(string group) { super.isSinkGroup(group) } } -/** - * Provides the query predicates needed to include a graph in a path-problem query. - */ -module PathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } - - /** Holds if `n` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode n, string key, string val) { - key = "semmle.label" and val = n.toString() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { - Subpaths::subpaths(arg, par, ret, out) - } -} - -/** - * An intermediate flow graph node. This is a triple consisting of a `Node`, - * a `CallContext`, and a `Configuration`. - */ -private class PathNodeMid extends PathNodeImpl, TPathNodeMid { - NodeEx node; - FlowState state; - CallContext cc; - SummaryCtx sc; - AccessPath ap; - Configuration config; - - PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - SummaryCtx getSummaryCtx() { result = sc } - - AccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - private PathNodeMid getSuccMid() { - pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx(), result.getAp()) and - result.getConfiguration() = unbindConf(this.getConfiguration()) - } - - override PathNodeImpl getASuccessorImpl() { - // an intermediate step to another intermediate node - result = this.getSuccMid() - or - // a final step to a sink - result = this.getSuccMid().projectToSink() - } - - override predicate isSource() { - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - } - - predicate isAtSink() { - sinkNode(node, state, config) and - ap instanceof AccessPathNil and - if hasSinkCallCtx(config) - then - // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` - // is exactly what we need to check. This also implies - // `sc instanceof SummaryCtxNone`. - // For `FeatureEqualSourceSinkCallContext` the initial call context was - // set to `CallContextSomeCall` and jumps are disallowed, so - // `cc instanceof CallContextNoCall` never holds. On the other hand, - // in this case there's never any need to enter a call except to identify - // a summary, so the condition in `pathIntoCallable` enforces this, which - // means that `sc instanceof SummaryCtxNone` holds if and only if we are - // in the call context of the source. - sc instanceof SummaryCtxNone or - cc instanceof CallContextNoCall - else any() - } - - PathNodeSink projectToSink() { - this.isAtSink() and - result.getNodeEx() = node and - result.getState() = state and - result.getConfiguration() = unbindConf(config) - } -} - -/** - * A flow graph node corresponding to a sink. This is disjoint from the - * intermediate nodes in order to uniquely correspond to a given sink by - * excluding the `CallContext`. - */ -private class PathNodeSink extends PathNodeImpl, TPathNodeSink { - NodeEx node; - FlowState state; - Configuration config; - - PathNodeSink() { this = TPathNodeSink(node, state, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result = TPathNodeSinkGroup(this.getSinkGroup(), config) - } - - override predicate isSource() { sourceNode(node, state, config) } - - string getSinkGroup() { config.sinkGrouping(node.asNode(), result) } -} - -private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { - string sourceGroup; - Configuration config; - - PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result.getSourceGroup() = sourceGroup and - result.getConfiguration() = config - } - - override predicate isSource() { none() } - - override string toString() { result = sourceGroup } - - override 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 - } -} - -private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { - string sinkGroup; - Configuration config; - - PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { none() } - - override predicate isSource() { none() } - - override string toString() { result = sinkGroup } - - override 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 - } -} - -private predicate pathNode( - PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, - Configuration conf, LocalCallContext localCC -) { - midnode = mid.getNodeEx() and - state = mid.getState() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - midnode.getEnclosingCallable()) and - ap = mid.getAp() -} - -/** - * Holds if data may flow from `mid` to `node`. The last step in or out of - * a callable is recorded by `cc`. - */ -pragma[assume_small_delta] -pragma[nomagic] -private predicate pathStep( - PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap -) { - exists(NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC | - pathNode(mid, midnode, state0, cc, sc, ap, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, true, _, conf, localCC) - ) - or - exists( - AccessPath ap0, NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC - | - pathNode(mid, midnode, state0, cc, sc, ap0, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), conf, - localCC) and - ap0 instanceof AccessPathNil - ) - or - jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - ap = mid.getAp() - or - additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, mid.getConfiguration()) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - pathIntoCallable(mid, node, state, _, cc, sc, _, _) and ap = mid.getAp() - or - pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone - or - pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() -} - -pragma[nomagic] -private predicate pathReadStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - tc = ap0.getHead() and - Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -pragma[nomagic] -private predicate pathStoreStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -private predicate pathOutOfCallable0( - PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa, - Configuration config -) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - apa = mid.getAp().getApprox() and - config = mid.getConfiguration() -} - -pragma[nomagic] -private predicate pathOutOfCallable1( - PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPathApprox apa, Configuration config -) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - pathOutOfCallable0(mid, pos, state, innercc, apa, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() +private predicate hasFlow(Node source, Node sink, Configuration config) { + exists(PathNode source0, PathNode sink0 | + hasFlowPath(source0, sink0, config) and + source0.getNode() = source and + sink0.getNode() = sink ) } -pragma[noinline] -private NodeEx getAnOutNodeFlow( - ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config -) { - result.asNode() = kind.getAnOutNode(call) and - Stage5::revFlow(result, _, apa, config) +private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) { + hasFlowPath(source, sink) and source.getConfiguration() = config } -/** - * Holds if data may flow from `mid` to `out`. The last step of this path - * is a return from a callable and is recorded by `cc`, if needed. - */ -pragma[noinline] -private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { - exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | - pathOutOfCallable1(mid, call, kind, state, cc, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} +private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) } -/** - * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. - */ -pragma[noinline] -private predicate pathIntoArg( - PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(ArgNodeEx arg, ArgumentPosition apos | - pathNode(mid, arg, state, cc, _, ap, config, _) and - arg.asNode().(ArgNode).argumentOf(call, apos) and - apa = ap.getApprox() and - parameterMatch(ppos, apos) - ) -} - -pragma[nomagic] -private predicate parameterCand( - DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::revFlow(p, _, apa, config) and - p.isParameterOf(callable, pos) - ) -} - -pragma[nomagic] -private predicate pathIntoCallable0( - PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, AccessPath ap, Configuration config -) { - exists(AccessPathApprox apa | - pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - callable = resolveCall(call, outercc) and - parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa), - pragma[only_bind_into](config)) - ) -} - -/** - * Holds if data may flow from `mid` to `p` through `call`. The contexts - * before and after entering the callable are `outercc` and `innercc`, - * respectively. - */ -pragma[nomagic] -private predicate pathIntoCallable( - PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, - SummaryCtx sc, DataFlowCall call, Configuration config -) { - exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | - pathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - ( - sc = TSummaryCtxSome(p, state, ap) - or - not exists(TSummaryCtxSome(p, state, ap)) and - sc = TSummaryCtxNone() and - // When the call contexts of source and sink needs to match then there's - // never any reason to enter a callable except to find a summary. See also - // the comment in `PathNodeMid::isAtSink`. - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) -} - -/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ -pragma[nomagic] -private predicate paramFlowsThrough( - ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, - AccessPathApprox apa, Configuration config -) { - exists(RetNodeEx ret | - pathNode(_, ret, state, cc, sc, ap, config, _) and - kind = ret.getKind() and - apa = ap.getApprox() and - parameterFlowThroughAllowed(sc.getParamNode(), kind) - ) -} - -pragma[nomagic] -private predicate pathThroughCallable0( - DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(CallContext innercc, SummaryCtx sc | - pathIntoCallable(mid, _, _, cc, innercc, sc, call, config) and - paramFlowsThrough(kind, state, innercc, sc, ap, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` through a callable to the node `out`. - * The context `cc` is restored to its value prior to entering the callable. - */ -pragma[noinline] -private predicate pathThroughCallable( - PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap -) { - exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config | - pathThroughCallable0(call, mid, kind, state, cc, ap, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -private module Subpaths { - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths01( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - exists(Configuration config | - pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and - pathIntoCallable(arg, par, _, _, innercc, sc, _, config) and - paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, - pragma[only_bind_into](apout), _, unbindConf(config)) and - not arg.isHidden() - ) - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `sout`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths02( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and - out.asNode() = kind.getAnOutNode(_) - } - - pragma[nomagic] - private Configuration getPathNodeConf(PathNodeImpl n) { result = n.getConfiguration() } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple. - */ - pragma[nomagic] - private predicate subpaths03( - PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, AccessPath apout - ) { - exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | - subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and - pathNode(ret, retnode, sout, innercc, sc, apout, unbindConf(getPathNodeConf(arg)), _) and - kind = retnode.getKind() - ) - } - - private PathNodeImpl localStepToHidden(PathNodeImpl n) { - n.getASuccessorImpl() = result and - result.isHidden() and - exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | - localFlowBigStep(n1, _, n2, _, _, _, _, _) or - store(n1, _, n2, _, _) or - readSet(n1, _, n2, _) - ) - } - - pragma[nomagic] - private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { - succ = pred.getANonHiddenSuccessor() and - succNode = succ.getNodeEx() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { - exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | - pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and - subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and - hasSuccessor(pragma[only_bind_into](arg), par, p) and - not ret.isHidden() and - pathNode(out0, o, sout, _, _, apout, _, _) - | - out = out0 or out = out0.projectToSink() - ) - } - - /** - * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. - */ - predicate retReach(PathNodeImpl n) { - exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) - or - exists(PathNodeImpl mid | - retReach(mid) and - n.getANonHiddenSuccessor() = mid and - not subpaths(_, mid, _, _) - ) - } -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -private predicate hasFlowPath( - PathNodeImpl flowsource, PathNodeImpl flowsink, Configuration configuration -) { - flowsource.isFlowSource() and - flowsource.getConfiguration() = configuration and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.isFlowSink() -} - -private predicate flowsTo( - PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink, - Configuration configuration -) { - flowsource.isSource() and - flowsource.getConfiguration() = configuration and - flowsource.getNodeEx().asNode() = source and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.getNodeEx().asNode() = sink -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -predicate flowsTo(Node source, Node sink, Configuration configuration) { - flowsTo(_, _, source, sink, configuration) -} - -private predicate finalStats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples -) { - fwd = true and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and - tuples = count(PathNodeImpl pn) - or - fwd = false and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and - tuples = count(PathNode pn) -} - -/** - * INTERNAL: Only for debugging. - * - * Calculates per-stage metrics for data flow. - */ -predicate stageStats( - int n, string stage, int nodes, int fields, int conscand, int states, int tuples, - Configuration config -) { - stage = "1 Fwd" and - n = 10 and - Stage1::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "1 Rev" and - n = 15 and - Stage1::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Fwd" and - n = 20 and - Stage2::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Rev" and - n = 25 and - Stage2::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Fwd" and - n = 30 and - Stage3::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Rev" and - n = 35 and - Stage3::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Fwd" and - n = 40 and - Stage4::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Rev" and - n = 45 and - Stage4::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Fwd" and - n = 50 and - Stage5::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Rev" and - n = 55 and - Stage5::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) - or - stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) -} - -private module FlowExploration { - private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - jumpStep(node1, node2, config) - or - additionalJumpStep(node1, node2, config) - or - additionalJumpStateStep(node1, _, node2, _, config) - or - // flow into callable - viableParamArgEx(_, node2, node1) - or - // flow out of a callable - viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) - | - c1 = node1.getEnclosingCallable() and - c2 = node2.getEnclosingCallable() and - c1 != c2 - ) - } - - private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSource(n) or config.isSource(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSrc(mid, config) and callableStep(mid, c, config) - ) - } - - private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSink(n) or config.isSink(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSink(mid, config) and callableStep(c, mid, config) - ) - } - - private newtype TCallableExt = - TCallable(DataFlowCallable c, Configuration config) { - interestingCallableSrc(c, config) or - interestingCallableSink(c, config) - } or - TCallableSrc() or - TCallableSink() - - private predicate callableExtSrc(TCallableSrc src) { any() } - - private predicate callableExtSink(TCallableSink sink) { any() } - - private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { - exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | - callableStep(c1, c2, config) and - ce1 = TCallable(c1, pragma[only_bind_into](config)) and - ce2 = TCallable(c2, pragma[only_bind_into](config)) - ) - or - exists(Node n, Configuration config | - ce1 = TCallableSrc() and - (config.isSource(n) or config.isSource(n, _)) and - ce2 = TCallable(getNodeEnclosingCallable(n), config) - ) - or - exists(Node n, Configuration config | - ce2 = TCallableSink() and - (config.isSink(n) or config.isSink(n, _)) and - ce1 = TCallable(getNodeEnclosingCallable(n), config) - ) - } - - private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { - callableExtStepFwd(ce2, ce1) - } - - private int distSrcExt(TCallableExt c) = - shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) - - private int distSinkExt(TCallableExt c) = - shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) - - private int distSrc(DataFlowCallable c, Configuration config) { - result = distSrcExt(TCallable(c, config)) - 1 - } - - private int distSink(DataFlowCallable c, Configuration config) { - result = distSinkExt(TCallable(c, config)) - 1 - } - - private newtype TPartialAccessPath = - TPartialNil(DataFlowType t) or - TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first - * element of the list and its length are tracked. If data flows from a source to - * a given node with a given `AccessPath`, this indicates the sequence of - * dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ - private class PartialAccessPath extends TPartialAccessPath { - abstract string toString(); - - TypedContent getHead() { this = TPartialCons(result, _) } - - int len() { - this = TPartialNil(_) and result = 0 - or - this = TPartialCons(_, result) - } - - DataFlowType getType() { - this = TPartialNil(result) - or - exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) - } - } - - private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { - override string toString() { - exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) - } - } - - private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { - override string toString() { - exists(TypedContent tc, int len | this = TPartialCons(tc, len) | - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private newtype TRevPartialAccessPath = - TRevPartialNil() or - TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `Content`s, but only the first - * element of the list and its length are tracked. - */ - private class RevPartialAccessPath extends TRevPartialAccessPath { - abstract string toString(); - - Content getHead() { this = TRevPartialCons(result, _) } - - int len() { - this = TRevPartialNil() and result = 0 - or - this = TRevPartialCons(_, result) - } - } - - private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { - override string toString() { result = "" } - } - - private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { - override string toString() { - exists(Content c, int len | this = TRevPartialCons(c, len) | - if len = 1 - then result = "[" + c.toString() + "]" - else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private predicate relevantState(FlowState state) { - sourceNode(_, state, _) or - sinkNode(_, state, _) or - additionalLocalStateStep(_, state, _, _, _) or - additionalLocalStateStep(_, _, _, state, _) or - additionalJumpStateStep(_, state, _, _, _) or - additionalJumpStateStep(_, _, _, state, _) - } - - private newtype TSummaryCtx1 = - TSummaryCtx1None() or - TSummaryCtx1Param(ParamNodeEx p) - - private newtype TSummaryCtx2 = - TSummaryCtx2None() or - TSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TSummaryCtx3 = - TSummaryCtx3None() or - TSummaryCtx3Some(PartialAccessPath ap) - - private newtype TRevSummaryCtx1 = - TRevSummaryCtx1None() or - TRevSummaryCtx1Some(ReturnPosition pos) - - private newtype TRevSummaryCtx2 = - TRevSummaryCtx2None() or - TRevSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TRevSummaryCtx3 = - TRevSummaryCtx3None() or - TRevSummaryCtx3Some(RevPartialAccessPath ap) - - private newtype TPartialPathNode = - TPartialPathNodeFwd( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = TPartialNil(node.getDataFlowType()) and - exists(config.explorationLimit()) - or - partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap, config) and - distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() - } or - TPartialPathNodeRev( - NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, - RevPartialAccessPath ap, Configuration config - ) { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() and - exists(config.explorationLimit()) - or - revPartialPathStep(_, node, state, sc1, sc2, sc3, ap, config) and - not clearsContentEx(node, ap.getHead()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead()) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() - } - - pragma[nomagic] - private predicate partialPathNodeMk0( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap, config) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - not clearsContentEx(node, ap.getHead().getContent()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead().getContent()) - ) and - if node.asNode() instanceof CastingNode - then compatibleTypes(node.getDataFlowType(), ap.getType()) - else any() - } - - /** - * A `Node` augmented with a call context, an access path, and a configuration. - */ - class PartialPathNode extends TPartialPathNode { - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() - } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - /** Gets the underlying `Node`. */ - final Node getNode() { this.getNodeEx().projectToNode() = result } - - FlowState getState() { none() } - - private NodeEx getNodeEx() { - result = this.(PartialPathNodeFwd).getNodeEx() or - result = this.(PartialPathNodeRev).getNodeEx() - } - - /** Gets the associated configuration. */ - Configuration getConfiguration() { none() } - - /** Gets a successor of this node, if any. */ - PartialPathNode getASuccessor() { none() } - - /** - * Gets the approximate distance to the nearest source measured in number - * of interprocedural steps. - */ - int getSourceDistance() { - result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - /** - * Gets the approximate distance to the nearest sink measured in number - * of interprocedural steps. - */ - int getSinkDistance() { - result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - private string ppAp() { - exists(string s | - s = this.(PartialPathNodeFwd).getAp().toString() or - s = this.(PartialPathNodeRev).getAp().toString() - | - if s = "" then result = "" else result = " " + s - ) - } - - private string ppCtx() { - result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" - } - - /** Holds if this is a source in a forward-flow path. */ - predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } - - /** Holds if this is a sink in a reverse-flow path. */ - predicate isRevSink() { this.(PartialPathNodeRev).isSink() } - } - - /** - * Provides the query predicates needed to include a graph in a path-problem query. - */ - module PartialPathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } - } - - private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { - NodeEx node; - FlowState state; - CallContext cc; - TSummaryCtx1 sc1; - TSummaryCtx2 sc2; - TSummaryCtx3 sc3; - PartialAccessPath ap; - Configuration config; - - PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - TSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TSummaryCtx3 getSummaryCtx3() { result = sc3 } - - PartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeFwd getASuccessor() { - partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp(), - result.getConfiguration()) - } - - predicate isSource() { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap instanceof TPartialNil - } - } - - private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { - NodeEx node; - FlowState state; - TRevSummaryCtx1 sc1; - TRevSummaryCtx2 sc2; - TRevSummaryCtx3 sc3; - RevPartialAccessPath ap; - Configuration config; - - PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } - - RevPartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeRev getASuccessor() { - revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), - this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp(), this.getConfiguration()) - } - - predicate isSink() { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() - } - } - - private predicate partialPathStep( - PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, - TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and - ( - localFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - ) - or - jumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - partialPathStoreStep(mid, _, _, node, ap) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(PartialAccessPath ap0, TypedContent tc | - partialPathReadStep(mid, ap0, tc, node, cc, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsFwd(ap, tc, ap0, config) - ) - or - partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap, config) - or - partialPathOutOfCallable(mid, node, state, cc, ap, config) and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() - or - partialPathThroughCallable(mid, node, state, cc, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - bindingset[result, i] - private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } - - pragma[inline] - private predicate partialPathStoreStep( - PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, - PartialAccessPath ap2 - ) { - exists(NodeEx midNode, DataFlowType contentType | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - store(midNode, tc, node, contentType, mid.getConfiguration()) and - ap2.getHead() = tc and - ap2.len() = unbindInt(ap1.len() + 1) and - compatibleTypes(ap1.getType(), contentType) - ) - } - - pragma[nomagic] - private predicate apConsFwd( - PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeFwd mid | - partialPathStoreStep(mid, ap1, tc, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate partialPathReadStep( - PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc, - Configuration config - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and - ap.getHead() = tc and - pragma[only_bind_into](config) = mid.getConfiguration() and - cc = mid.getCallContext() - ) - } - - private predicate partialPathOutOfCallable0( - PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, - PartialAccessPath ap, Configuration config - ) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - ap = mid.getAp() and - config = mid.getConfiguration() - } - - pragma[nomagic] - private predicate partialPathOutOfCallable1( - PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - partialPathOutOfCallable0(mid, pos, state, innercc, ap, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) - } - - private predicate partialPathOutOfCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(ReturnKindExt kind, DataFlowCall call | - partialPathOutOfCallable1(mid, call, kind, state, cc, ap, config) - | - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[noinline] - private predicate partialPathIntoArg( - PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ArgNode arg, ArgumentPosition apos | - arg = mid.getNodeEx().asNode() and - state = mid.getState() and - cc = mid.getCallContext() and - arg.argumentOf(call, apos) and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate partialPathIntoCallable0( - PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - partialPathIntoArg(mid, pos, state, outercc, call, ap, config) and - callable = resolveCall(call, outercc) - } - - private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, - CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ParameterPosition pos, DataFlowCallable callable | - partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - sc1 = TSummaryCtx1Param(p) and - sc2 = TSummaryCtx2Some(state) and - sc3 = TSummaryCtx3Some(ap) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) - } - - pragma[nomagic] - private predicate paramFlowsThroughInPartialPath( - ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeFwd mid, RetNodeEx ret | - mid.getNodeEx() = ret and - kind = ret.getKind() and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() and - ap = mid.getAp() - ) - } - - pragma[noinline] - private predicate partialPathThroughCallable0( - DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | - partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _, config) and - paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap, config) - ) - } - - private predicate partialPathThroughCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ReturnKindExt kind | - partialPathThroughCallable0(call, mid, kind, state, cc, ap, config) and - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[nomagic] - private predicate revPartialPathStep( - PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, - TRevSummaryCtx3 sc3, RevPartialAccessPath ap, Configuration config - ) { - localFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - jumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - revPartialPathReadStep(mid, _, _, node, ap) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(RevPartialAccessPath ap0, Content c | - revPartialPathStoreStep(mid, ap0, c, node, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsRev(ap, c, ap0, config) - ) - or - exists(ParamNodeEx p | - mid.getNodeEx() = p and - viableParamArgEx(_, p, node) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - or - exists(ReturnPosition pos | - revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap, config) and - pos = getReturnPosition(node.asNode()) - ) - or - revPartialPathThroughCallable(mid, node, state, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - pragma[inline] - private predicate revPartialPathReadStep( - PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, - RevPartialAccessPath ap2 - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - read(node, c, midNode, mid.getConfiguration()) and - ap2.getHead() = c and - ap2.len() = unbindInt(ap1.len() + 1) - ) - } - - pragma[nomagic] - private predicate apConsRev( - RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeRev mid | - revPartialPathReadStep(mid, ap1, c, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathStoreStep( - PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config - ) { - exists(NodeEx midNode, TypedContent tc | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - store(node, tc, midNode, _, config) and - ap.getHead() = c and - config = mid.getConfiguration() and - tc.getContent() = c - ) - } - - pragma[nomagic] - private predicate revPartialPathIntoReturn( - PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, - TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap, - Configuration config - ) { - exists(NodeEx out | - mid.getNodeEx() = out and - mid.getState() = state and - viableReturnPosOutEx(call, pos, out) and - sc1 = TRevSummaryCtx1Some(pos) and - sc2 = TRevSummaryCtx2Some(state) and - sc3 = TRevSummaryCtx3Some(ap) and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathFlowsThrough( - ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, - TRevSummaryCtx3Some sc3, RevPartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | - mid.getNodeEx() = p and - mid.getState() = state and - p.getPosition() = ppos and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable0( - DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, - RevPartialAccessPath ap, Configuration config - ) { - exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | - revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _, config) and - revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap, config) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ArgumentPosition pos | - revPartialPathThroughCallable0(call, mid, pos, state, ap, config) and - node.asNode().(ArgNode).argumentOf(call, pos) - ) - } -} - -import FlowExploration - -private predicate partialFlow( - PartialPathNode source, PartialPathNode node, Configuration configuration -) { - source.getConfiguration() = configuration and - source.isFwdSource() and - node = source.getASuccessor+() -} - -private predicate revPartialFlow( - PartialPathNode node, PartialPathNode sink, Configuration configuration -) { - sink.getConfiguration() = configuration and - sink.isRevSink() and - node.getASuccessor+() = sink -} +predicate flowsTo = hasFlow/3; diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl5.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl5.qll index 1b969756b09..e6bdc74cceb 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl5.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl5.qll @@ -1,16 +1,16 @@ /** - * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from - * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed - * through the `Configuration` class. This file exists in several identical - * copies, allowing queries to use multiple `Configuration` classes that depend - * on each other without introducing mutual recursion among those configurations. + * DEPRECATED: Use `Make` and `MakeWithState` instead. + * + * Provides a `Configuration` class backwards-compatible interface to the data + * flow library. */ private import DataFlowImplCommon private import DataFlowImplSpecific::Private import DataFlowImplSpecific::Public +private import DataFlowImpl import DataFlowImplCommonPublic +import FlowStateString /** * A configuration of interprocedural data flow analysis. This defines @@ -144,6 +144,10 @@ abstract class Configuration extends string { * - `FeatureEqualSourceSinkCallContext`: * Implies both of the above and additionally ensures that the entire flow * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. */ FlowFeature getAFeature() { none() } @@ -156,7 +160,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from `source` to `sink` for this configuration. */ - predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } + predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) } /** * Holds if data may flow from `source` to `sink` for this configuration. @@ -169,9 +173,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from some source to `sink` for this configuration. */ - predicate hasFlowTo(Node sink) { - sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode() - } + predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) } /** * Holds if data may flow from some source to `sink` for this configuration. @@ -179,10 +181,12 @@ abstract class Configuration extends string { predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } /** + * DEPRECATED: Use `FlowExploration` instead. + * * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` * measured in approximate number of interprocedural steps. */ - int explorationLimit() { none() } + deprecated int explorationLimit() { none() } /** * Holds if hidden nodes should be included in the data flow graph. @@ -191,49 +195,6 @@ abstract class Configuration extends string { * is not visualized (for example in a `path-problem` query). */ predicate includeHiddenNodes() { none() } - - /** - * Holds if there is a partial data flow path from `source` to `node`. The - * approximate distance between `node` and the closest source is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards sink definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sources is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - */ - final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { - partialFlow(source, node, this) and - dist = node.getSourceDistance() - } - - /** - * Holds if there is a partial data flow path from `node` to `sink`. The - * approximate distance between `node` and the closest sink is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards source definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sinks is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - * - * Note that reverse flow has slightly lower precision than the corresponding - * forward flow, as reverse flow disregards type pruning among other features. - */ - final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { - revPartialFlow(node, sink, this) and - dist = node.getSinkDistance() - } } /** @@ -263,90 +224,6 @@ abstract private class ConfigurationRecursionPrevention extends Configuration { } } -private newtype TNodeEx = - TNodeNormal(Node n) or - TNodeImplicitRead(Node n, boolean hasRead) { - any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true] - } - -private class NodeEx extends TNodeEx { - string toString() { - result = this.asNode().toString() - or - exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") - } - - Node asNode() { this = TNodeNormal(result) } - - predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } - - Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } - - pragma[nomagic] - private DataFlowCallable getEnclosingCallable0() { - nodeEnclosingCallable(this.projectToNode(), result) - } - - pragma[inline] - DataFlowCallable getEnclosingCallable() { - pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) - } - - pragma[nomagic] - private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } - - pragma[inline] - DataFlowType getDataFlowType() { - pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) - } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -private class ArgNodeEx extends NodeEx { - ArgNodeEx() { this.asNode() instanceof ArgNode } -} - -private class ParamNodeEx extends NodeEx { - ParamNodeEx() { this.asNode() instanceof ParamNode } - - predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { - this.asNode().(ParamNode).isParameterOf(c, pos) - } - - ParameterPosition getPosition() { this.isParameterOf(_, result) } -} - -private class RetNodeEx extends NodeEx { - RetNodeEx() { this.asNode() instanceof ReturnNodeExt } - - ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } - - ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } -} - -private predicate inBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierIn(n) - | - config.isSource(n) or config.isSource(n, _) - ) -} - -private predicate outBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierOut(n) - | - config.isSink(n) or config.isSink(n, _) - ) -} - /** A bridge class to access the deprecated `isBarrierGuard`. */ private class BarrierGuardGuardedNodeBridge extends Unit { abstract predicate guardedNode(Node n, Configuration config); @@ -370,3019 +247,94 @@ private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { } } -pragma[nomagic] -private predicate fullBarrier(NodeEx node, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n) +private FlowState relevantState(Configuration config) { + config.isSource(_, result) or + config.isSink(_, result) or + config.isBarrier(_, result) or + config.isAdditionalFlowStep(_, result, _, _) or + config.isAdditionalFlowStep(_, _, _, result) +} + +private newtype TConfigState = + TMkConfigState(Configuration config, FlowState state) { + state = relevantState(config) or state instanceof FlowStateEmpty + } + +private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) } + +private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) } + +private predicate singleConfiguration() { 1 = strictcount(Configuration c) } + +private module Config implements FullStateConfigSig { + class FlowState = TConfigState; + + predicate isSource(Node source, FlowState state) { + getConfig(state).isSource(source, getState(state)) or - config.isBarrierIn(n) and - not config.isSource(n) and - not config.isSource(n, _) + getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty + } + + predicate isSink(Node sink, FlowState state) { + getConfig(state).isSink(sink, getState(state)) or - config.isBarrierOut(n) and - not config.isSink(n) and - not config.isSink(n, _) + getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty + } + + predicate isBarrier(Node node) { none() } + + predicate isBarrier(Node node, FlowState state) { + getConfig(state).isBarrier(node, getState(state)) or + getConfig(state).isBarrier(node) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state)) + } + + predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) } + + predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) } + + predicate isAdditionalFlowStep(Node node1, Node node2) { + singleConfiguration() and + any(Configuration config).isAdditionalFlowStep(node1, node2) + } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and + getConfig(state2) = getConfig(state1) or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config) - ) -} - -pragma[nomagic] -private predicate stateBarrier(NodeEx node, FlowState state, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n, state) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config) - ) -} - -pragma[nomagic] -private predicate sourceNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSource(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSource(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -pragma[nomagic] -private predicate sinkNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSink(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSink(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -/** Provides the relevant barriers for a step from `node1` to `node2`. */ -pragma[inline] -private predicate stepFilter(NodeEx node1, NodeEx node2, Configuration config) { - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) -} - -/** - * Holds if data can flow in one local step from `node1` to `node2`. - */ -private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.asNode() = n and - node2.isImplicitReadNode(n, false) and - not fullBarrier(node1, config) - ) -} - -/** - * Holds if the additional step from `node1` to `node2` does not jump between callables. - */ -private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.isImplicitReadNode(n, true) and - node2.asNode() = n and - not fullBarrier(node2, config) - ) -} - -private predicate additionalLocalStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) - ) -} - -/** - * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. - */ -private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if the additional step from `node1` to `node2` jumps between callables. - */ -private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate additionalJumpStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -pragma[nomagic] -private predicate readSet(NodeEx node1, ContentSet c, NodeEx node2, Configuration config) { - readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and - stepFilter(node1, node2, config) - or - exists(Node n | - node2.isImplicitReadNode(n, true) and - node1.isImplicitReadNode(n, _) and - config.allowImplicitRead(n, c) - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(ContentSet cs | - readSet(node1, cs, node2, config) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate clearsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - clearsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate expectsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - expectsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -pragma[nomagic] -private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } - -pragma[nomagic] -private predicate hasReadStep(Content c, Configuration config) { read(_, c, _, config) } - -pragma[nomagic] -private predicate store( - NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config -) { - store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), - contentType) and - hasReadStep(tc.getContent(), config) and - stepFilter(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { - viableReturnPosOut(call, pos, out.asNode()) -} - -pragma[nomagic] -private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { - viableParamArg(call, p.asNode(), arg.asNode()) -} - -/** - * Holds if field flow should be used for the given configuration. - */ -private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } - -private predicate hasSourceCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSourceCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate hasSinkCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSinkCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if flow from `p` to a return node of kind `kind` is allowed. - * - * We don't expect a parameter to return stored in itself, unless - * explicitly allowed - */ -bindingset[p, kind] -private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { - exists(ParameterPosition pos | p.isParameterOf(_, pos) | - not kind.(ParamUpdateReturnKind).getPosition() = pos - or - allowParameterReturnInSelfCached(p.asNode()) - ) -} - -private module Stage1 implements StageSig { - class Ap extends int { - // workaround for bad functionality-induced joins (happens when using `Unit`) - pragma[nomagic] - Ap() { this in [0 .. 1] and this < 1 } - } - - private class Cc = boolean; - - /* Begin: Stage 1 logic. */ - /** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `cc` records whether the node is reached through an - * argument in a call. - */ - private predicate fwdFlow(NodeEx node, Cc cc, Configuration config) { - sourceNode(node, _, config) and - if hasSourceCallCtx(config) then cc = true else cc = false - or - exists(NodeEx mid | fwdFlow(mid, cc, config) | - localFlowStep(mid, node, config) or - additionalLocalFlowStep(mid, node, config) or - additionalLocalStateStep(mid, _, node, _, config) - ) - or - exists(NodeEx mid | fwdFlow(mid, _, config) and cc = false | - jumpStep(mid, node, config) or - additionalJumpStep(mid, node, config) or - additionalJumpStateStep(mid, _, node, _, config) - ) - or - // store - exists(NodeEx mid | - useFieldFlow(config) and - fwdFlow(mid, cc, config) and - store(mid, _, node, _, config) - ) - or - // read - exists(ContentSet c | - fwdFlowReadSet(c, node, cc, config) and - fwdFlowConsCandSet(c, _, config) - ) - or - // flow into a callable - fwdFlowIn(_, _, _, node, config) and - cc = true - or - // flow out of a callable - fwdFlowOut(_, node, false, config) and - cc = false - or - // flow through a callable - exists(DataFlowCall call | - fwdFlowOutFromArg(call, node, config) and - fwdFlowIsEntered(call, cc, config) - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowIn( - DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p, Configuration config - ) { - // call context cannot help reduce virtual dispatch - fwdFlow(arg, cc, config) and - viableParamArgEx(call, p, arg) and - not fullBarrier(p, config) and - ( - cc = false - or - cc = true and - not reducedViableImplInCallContext(call, _, _) - ) - or - // call context may help reduce virtual dispatch - exists(DataFlowCallable target | - fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target, config) and - target = viableImplInSomeFwdFlowCallContextExt(call, config) and - cc = true - ) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - fwdFlowIn(call, _, cc, _, config) - } - - pragma[nomagic] - private predicate fwdFlowInReducedViableImplInSomeCallContext( - DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target, Configuration config - ) { - fwdFlow(arg, true, config) and - viableParamArgEx(call, p, arg) and - reducedViableImplInCallContext(call, _, _) and - target = p.getEnclosingCallable() and - not fullBarrier(p, config) - } - - /** - * Gets a viable dispatch target of `call` in the context `ctx`. This is - * restricted to those `call`s for which a context might make a difference, - * and to `ctx`s that are reachable in `fwdFlow`. - */ - pragma[nomagic] - private DataFlowCallable viableImplInSomeFwdFlowCallContextExt( - DataFlowCall call, Configuration config - ) { - exists(DataFlowCall ctx | - fwdFlowIsEntered(ctx, _, config) and - result = viableImplInCallContextExt(call, ctx) - ) - } - - private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) } - - pragma[nomagic] - private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc, Configuration config) { - exists(NodeEx mid | - fwdFlow(mid, cc, config) and - readSet(mid, c, node, config) - ) - } - - /** - * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - fwdFlow(mid, _, config) and - store(mid, tc, node, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `cs` may be interpreted in a read as the target of some store - * into `c`, in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCandSet(ContentSet cs, Content c, Configuration config) { - fwdFlowConsCand(c, config) and - c = cs.getAReadContent() - } - - pragma[nomagic] - private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { - exists(RetNodeEx ret | - fwdFlow(ret, cc, config) and - ret.getReturnPosition() = pos - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) { - exists(ReturnPosition pos | - fwdFlowReturnPosition(pos, cc, config) and - viableReturnPosOutEx(call, pos, out) and - not fullBarrier(out, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) { - fwdFlowOut(call, out, true, config) - } - - private predicate stateStepFwd(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1 | - additionalLocalStateStep(node1, state1, _, state2, config) or - additionalJumpStateStep(node1, state1, _, state2, config) - | - fwdFlow(node1, config) - ) - } - - private predicate fwdFlowState(FlowState state, Configuration config) { - sourceNode(_, state, config) - or - exists(FlowState state0 | - fwdFlowState(state0, config) and - stateStepFwd(state0, state, config) - ) - } - - /** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink. - */ - pragma[nomagic] - private predicate revFlow(NodeEx node, boolean toReturn, Configuration config) { - revFlow0(node, toReturn, config) and - fwdFlow(node, config) - } - - pragma[nomagic] - private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) { - exists(FlowState state | - fwdFlow(node, pragma[only_bind_into](config)) and - sinkNode(node, state, config) and - fwdFlowState(state, pragma[only_bind_into](config)) and - if hasSinkCallCtx(config) then toReturn = true else toReturn = false - ) - or - exists(NodeEx mid | revFlow(mid, toReturn, config) | - localFlowStep(node, mid, config) or - additionalLocalFlowStep(node, mid, config) or - additionalLocalStateStep(node, _, mid, _, config) - ) - or - exists(NodeEx mid | revFlow(mid, _, config) and toReturn = false | - jumpStep(node, mid, config) or - additionalJumpStep(node, mid, config) or - additionalJumpStateStep(node, _, mid, _, config) - ) - or - // store - exists(Content c | - revFlowStore(c, node, toReturn, config) and - revFlowConsCand(c, config) - ) - or - // read - exists(NodeEx mid, ContentSet c | - readSet(node, c, mid, config) and - fwdFlowConsCandSet(c, _, pragma[only_bind_into](config)) and - revFlow(mid, toReturn, pragma[only_bind_into](config)) - ) - or - // flow into a callable - revFlowIn(_, node, false, config) and - toReturn = false - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(pos, config) and - node.(RetNodeEx).getReturnPosition() = pos and - toReturn = true - ) - or - // flow through a callable - exists(DataFlowCall call | - revFlowInToReturn(call, node, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - /** - * Holds if `c` is the target of a read in the flow covered by `revFlow`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, ContentSet cs | - fwdFlow(node, pragma[only_bind_into](config)) and - readSet(node, cs, mid, config) and - fwdFlowConsCandSet(cs, c, pragma[only_bind_into](config)) and - revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) { - exists(NodeEx mid, TypedContent tc | - revFlow(mid, toReturn, pragma[only_bind_into](config)) and - fwdFlowConsCand(c, pragma[only_bind_into](config)) and - store(node, tc, mid, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `c` is the target of both a read and a store in the flow covered - * by `revFlow`. - */ - pragma[nomagic] - additional predicate revFlowIsReadAndStored(Content c, Configuration conf) { - revFlowConsCand(c, conf) and - revFlowStore(c, _, _, conf) - } - - pragma[nomagic] - additional predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config - ) { - fwdFlowReturnPosition(pos, _, config) and - viableReturnPosOutEx(call, pos, out) - } - - pragma[nomagic] - private predicate revFlowOut(ReturnPosition pos, Configuration config) { - exists(NodeEx out | - revFlow(out, _, config) and - viableReturnPosOutNodeCandFwd1(_, pos, out, config) - ) - } - - pragma[nomagic] - additional predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config - ) { - fwdFlowIn(call, arg, _, p, config) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate revFlowIn( - DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config - ) { - exists(ParamNodeEx p | - revFlow(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) { - revFlowIn(call, arg, true, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(NodeEx out | - revFlow(out, toReturn, config) and - fwdFlowOutFromArg(call, out, config) - ) - } - - private predicate stateStepRev(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - additionalLocalStateStep(node1, state1, node2, state2, config) or - additionalJumpStateStep(node1, state1, node2, state2, config) - | - revFlow(node1, _, pragma[only_bind_into](config)) and - revFlow(node2, _, pragma[only_bind_into](config)) and - fwdFlowState(state1, pragma[only_bind_into](config)) and - fwdFlowState(state2, pragma[only_bind_into](config)) - ) - } - - additional predicate revFlowState(FlowState state, Configuration config) { - exists(NodeEx node | - sinkNode(node, state, config) and - revFlow(node, _, pragma[only_bind_into](config)) and - fwdFlowState(state, pragma[only_bind_into](config)) - ) - or - exists(FlowState state0 | - revFlowState(state0, config) and - stateStepRev(state, state0, config) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Content c | - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - revFlow(node2, pragma[only_bind_into](config)) and - store(node1, tc, node2, contentType, config) and - c = tc.getContent() and - exists(ap1) - ) - } - - pragma[nomagic] - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) { - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - read(n1, c, n2, pragma[only_bind_into](config)) and - revFlow(n2, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, config) and - exists(ap) - } - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, _, pragma[only_bind_into](config)) and - exists(state) and - exists(ap) - } - - private predicate throughFlowNodeCand(NodeEx node, Configuration config) { - revFlow(node, true, config) and - fwdFlow(node, true, config) and - not inBarrier(node, config) and - not outBarrier(node, config) - } - - /** Holds if flow may return from `callable`. */ - pragma[nomagic] - private predicate returnFlowCallableNodeCand( - DataFlowCallable callable, ReturnKindExt kind, Configuration config - ) { - exists(RetNodeEx ret | - throughFlowNodeCand(ret, config) and - callable = ret.getEnclosingCallable() and - kind = ret.getKind() - ) - } - - /** - * Holds if flow may enter through `p` and reach a return node making `p` a - * candidate for the origin of a summary. - */ - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(DataFlowCallable c, ReturnKindExt kind | - throughFlowNodeCand(p, config) and - returnFlowCallableNodeCand(c, kind, config) and - p.getEnclosingCallable() = c and - exists(ap) and - parameterFlowThroughAllowed(p, kind) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - throughFlowNodeCand(ret, config) and - kind = ret.getKind() and - exists(argAp) and - exists(ap) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, boolean toReturn | - revFlow(arg, toReturn, config) and - revFlowInToReturn(call, arg, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, config)) and - fields = count(Content f0 | fwdFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | fwdFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, config)) and - fields = count(Content f0 | revFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | revFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | revFlow(n, b, config)) - } - /* End: Stage 1 logic. */ -} - -pragma[noinline] -private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - localFlowStep(node1, node2, config) -} - -pragma[noinline] -private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - additionalLocalFlowStep(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCand1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config -) { - Stage1::revFlow(out, config) and - Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, Configuration config -) { - exists(ReturnPosition pos | - viableReturnPosOutNodeCand1(call, pos, out, config) and - pos = ret.getReturnPosition() and - kind = pos.getKind() and - Stage1::revFlow(ret, config) and - not outBarrier(ret, config) and - not inBarrier(out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCand1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config -) { - Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and - Stage1::revFlow(arg, config) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config -) { - viableParamArgNodeCand1(call, p, arg, config) and - Stage1::revFlow(p, config) and - not outBarrier(arg, config) and - not inBarrier(p, config) -} - -/** - * Gets the amount of forward branching on the origin of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int branch(NodeEx n1, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n1, _, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) - ) -} - -/** - * Gets the amount of backward branching on the target of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int join(NodeEx n2, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n, _, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) - ) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. The - * `allowsFieldFlow` flag indicates whether the branching is within the limit - * specified by the configuration. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, ret, kind, out, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(ret, pragma[only_bind_into](config)) and - j = join(out, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. The `allowsFieldFlow` flag indicates whether - * the branching is within the limit specified by the configuration. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config -) { - flowIntoCallNodeCand1(call, arg, p, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(arg, pragma[only_bind_into](config)) and - j = join(p, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -private signature module StageSig { - class Ap; - - predicate revFlow(NodeEx node, Configuration config); - - predicate revFlowAp(NodeEx node, Ap ap, Configuration config); - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config); - - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config); - - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config); - - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ); - - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ); - - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config); -} - -private module MkStage { - class ApApprox = PrevStage::Ap; - - signature module StageParam { - class Ap; - - class ApNil extends Ap; - - bindingset[result, ap] - ApApprox getApprox(Ap ap); - - ApNil getApNil(NodeEx node); - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail); - - /** - * An approximation of `Content` that corresponds to the precision level of - * `Ap`, such that the mappings from both `Ap` and `Content` to this type - * are functional. - */ - class ApHeadContent; - - ApHeadContent getHeadContent(Ap ap); - - ApHeadContent projectToHeadContent(Content c); - - class ApOption; - - ApOption apNone(); - - ApOption apSome(Ap ap); - - class Cc; - - class CcCall extends Cc; - - // TODO: member predicate on CcCall - predicate matchesCall(CcCall cc, DataFlowCall call); - - class CcNoCall extends Cc; - - Cc ccNone(); - - CcCall ccSomeCall(); - - class LocalCc; - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc); - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ); - - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config - ); - - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config - ); - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config); - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType); - } - - module Stage implements StageSig { - import Param - - /* Begin: Stage logic. */ - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - private predicate revFlowApAlias(NodeEx node, ApApprox apa, Configuration config) { - PrevStage::revFlowAp(node, apa, config) - } - - pragma[nomagic] - private predicate flowIntoCallApa( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa, - Configuration config - ) { - flowIntoCall(call, arg, p, allowsFieldFlow, config) and - PrevStage::revFlowAp(p, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(arg, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowOutOfCallApa( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - ApApprox apa, Configuration config - ) { - flowOutOfCall(call, ret, kind, out, allowsFieldFlow, config) and - PrevStage::revFlowAp(out, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(ret, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowThroughOutOfCall( - DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, - ApApprox argApa, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa, pragma[only_bind_into](config)) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) and - matchesCall(ccc, call) - ) - } - - /** - * Holds if `node` is reachable with access path `ap` from a source in the - * configuration `config`. - * - * The call context `cc` records whether the node is reached through an - * argument in a call, and if so, `summaryCtx` and `argAp` record the - * corresponding parameter position and access path of that argument, respectively. - */ - pragma[nomagic] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa, config) and - PrevStage::revFlow(node, state, apa, config) and - filter(node, state, ap, config) - } - - pragma[inline] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, cc, summaryCtx, argAp, ap, _, config) - } - - pragma[nomagic] - private predicate fwdFlow0( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - sourceNode(node, state, config) and - (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and - argAp = apNone() and - summaryCtx = TParamNodeNone() and - ap = getApNil(node) and - apa = getApprox(ap) - or - exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | - fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0, config) and - localCc = getLocalCc(mid, cc) - | - localStep(mid, state0, node, state, true, _, config, localCc) and - ap = ap0 and - apa = apa0 - or - localStep(mid, state0, node, state, false, ap, config, localCc) and - ap0 instanceof ApNil and - apa = getApprox(ap) - ) - or - exists(NodeEx mid | - fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa, pragma[only_bind_into](config)) and - jumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(mid, state, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(mid, state0, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStateStep(mid, state0, node, state, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - // store - exists(TypedContent tc, Ap ap0 | - fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp, config) and - ap = apCons(tc, ap0) and - apa = getApprox(ap) - ) - or - // read - exists(Ap ap0, Content c | - fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp, config) and - fwdFlowConsCand(ap0, c, ap, config) and - apa = getApprox(ap) - ) - or - // flow into a callable - fwdFlowIn(_, node, state, _, cc, _, _, ap, apa, config) and - if PrevStage::parameterMayFlowThrough(node, apa, config) - then ( - summaryCtx = TParamNodeSome(node.asNode()) and - argAp = apSome(ap) - ) else ( - summaryCtx = TParamNodeNone() and argAp = apNone() - ) - or - // flow out of a callable - exists( - DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, - DataFlowCallable inner - | - fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa, config) and - flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa, config) and - inner = ret.getEnclosingCallable() and - cc = getCallContextReturn(inner, call, innercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - or - // flow through a callable - exists( - DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, ApApprox innerArgApa - | - fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowStore( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(DataFlowType contentType, ApApprox apa1 | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1, config) and - PrevStage::storeStepCand(node1, apa1, tc, node2, contentType, config) and - typecheckStore(ap1, contentType) - ) - } - - /** - * Holds if forward flow with access path `tail` reaches a store of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(TypedContent tc | - fwdFlowStore(_, tail, tc, _, _, _, _, _, config) and - tc.getContent() = c and - cons = apCons(tc, tail) - ) - } - - pragma[nomagic] - private predicate readStepCand( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - PrevStage::readStepCand(node1, c, node2, config) and - apc = projectToHeadContent(c) - } - - bindingset[node1, apc] - pragma[inline_late] - private predicate readStepCand0( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - readStepCand(node1, apc, c, node2, config) - } - - pragma[nomagic] - private predicate fwdFlowRead( - Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(ApHeadContent apc | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap, config) and - apc = getHeadContent(ap) and - readStepCand0(node1, apc, c, node2, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowIn( - DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, - ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa, Configuration config - ) { - exists(ArgNodeEx arg, boolean allowsFieldFlow | - fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa, config) and - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowRetFromArg( - RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, ApApprox argApa, - Ap ap, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - fwdFlow(pragma[only_bind_into](ret), state, ccc, - TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), - pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa), - pragma[only_bind_into](config)) and - kind = ret.getKind() and - parameterFlowThroughAllowed(summaryCtx, kind) and - argApa = getApprox(argAp) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) - ) - } - - pragma[inline] - private predicate fwdFlowThrough0( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, - Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa, config) and - fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp, config) - } - - pragma[nomagic] - private predicate fwdFlowThrough( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa, - config) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` - * and data might flow through the target callable and back out at `call`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered( - DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, - ParamNodeEx p, Ap ap, Configuration config - ) { - exists(ApApprox apa | - fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(p, apa, config) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate storeStepFwd( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config - ) { - fwdFlowStore(node1, ap1, tc, node2, _, _, _, _, config) and - ap2 = apCons(tc, ap1) and - fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _, config) - } - - private predicate readStepFwd( - NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config - ) { - fwdFlowRead(ap1, c, n1, n2, _, _, _, _, config) and - fwdFlowConsCand(ap1, c, ap2, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough0( - DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, - ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, - innerArgApa, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough( - RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, - Ap ap, Configuration config - ) { - exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | - returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa, config) and - pos = ret.getReturnPosition() and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowThroughIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap, - Configuration config - ) { - exists(ApApprox argApa | - flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), - allowsFieldFlow, argApa, pragma[only_bind_into](config)) and - fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa, - pragma[only_bind_into](config)) and - returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap, - pragma[only_bind_into](config)) and - if allowsFieldFlow = false then argAp instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowIntoCallAp( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap, - Configuration config - ) { - exists(ApApprox apa | - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - fwdFlow(arg, _, _, _, _, ap, apa, config) - ) - } - - pragma[nomagic] - private predicate flowOutOfCallAp( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, - Ap ap, Configuration config - ) { - exists(ApApprox apa | - flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa, config) and - fwdFlow(ret, _, _, _, _, ap, apa, config) and - pos = ret.getReturnPosition() - ) - } - - /** - * Holds if `node` with access path `ap` is part of a path from a source to a - * sink in the configuration `config`. - * - * The parameter `returnCtx` records whether (and how) the node must be returned - * from the enclosing callable in order to reach a sink, and if so, `returnAp` - * records the access path of the returned value. - */ - pragma[nomagic] - additional predicate revFlow( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - revFlow0(node, state, returnCtx, returnAp, ap, config) and - fwdFlow(node, state, _, _, _, ap, config) - } - - pragma[nomagic] - private predicate revFlow0( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, _, _, _, ap, config) and - sinkNode(node, state, config) and - ( - if hasSinkCallCtx(config) - then returnCtx = TReturnCtxNoFlowThrough() - else returnCtx = TReturnCtxNone() - ) and - returnAp = apNone() and - ap instanceof ApNil - or - exists(NodeEx mid, FlowState state0 | - localStep(node, state, mid, state0, true, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, ap, config) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap, pragma[only_bind_into](config)) and - localStep(node, pragma[only_bind_into](state), mid, state0, false, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, nil, pragma[only_bind_into](config)) and - ap instanceof ApNil - ) - or - exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, state, _, _, ap, config) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStep(node, mid, config) and - revFlow(pragma[only_bind_into](mid), state, _, _, nil, pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStateStep(node, state, mid, state0, config) and - revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil, - pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - // store - exists(Ap ap0, Content c | - revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp, config) and - revFlowConsCand(ap0, c, ap, config) - ) - or - // read - exists(NodeEx mid, Ap ap0 | - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - readStepFwd(node, ap, _, mid, ap0, config) - ) - or - // flow into a callable - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, state, TReturnCtxNone(), returnAp, ap, config) and - flowIntoCallAp(_, node, p, allowsFieldFlow, ap, config) and - (if allowsFieldFlow = false then ap instanceof ApNil else any()) and - returnCtx = TReturnCtxNone() - ) - or - // flow through a callable - exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, node, p, _, ap, innerReturnAp, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(_, node, pos, state, _, _, ap, config) and - if returnFlowsThrough(node, pos, state, _, _, _, ap, config) - then ( - returnCtx = TReturnCtxMaybeFlowThrough(pos) and - returnAp = apSome(ap) - ) else ( - returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() - ) - ) - } - - pragma[nomagic] - private predicate revFlowStore( - Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, - ReturnCtx returnCtx, ApOption returnAp, Configuration config - ) { - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - storeStepFwd(node, ap, tc, mid, ap0, config) and - tc.getContent() = c - } - - /** - * Holds if reverse flow with access path `tail` reaches a read of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(NodeEx mid, Ap tail0 | - revFlow(mid, _, _, _, tail, config) and - tail = pragma[only_bind_into](tail0) and - readStepFwd(_, cons, c, mid, tail0, config) - ) - } - - pragma[nomagic] - private predicate revFlowOut( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, - ApOption returnAp, Ap ap, Configuration config - ) { - exists(NodeEx out, boolean allowsFieldFlow | - revFlow(out, state, returnCtx, returnAp, ap, config) and - flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate revFlowParamToReturn( - ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap, Configuration config - ) { - revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), - pragma[only_bind_into](ap), pragma[only_bind_into](config)) and - parameterFlowThroughAllowed(p, pos.getKind()) and - PrevStage::parameterMayFlowThrough(p, getApprox(ap), config) - } - - pragma[nomagic] - private predicate revFlowThrough( - DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, - ApOption returnAp, Ap ap, Ap innerReturnAp, Configuration config - ) { - revFlowParamToReturn(p, state, pos, innerReturnAp, ap, config) and - revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned( - DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap, - Configuration config - ) { - exists(RetNodeEx ret, FlowState state, CcCall ccc | - revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap, config) and - returnFlowsThrough(ret, pos, state, ccc, _, _, ap, config) and - matchesCall(ccc, call) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Ap ap2, Content c | - PrevStage::storeStepCand(node1, _, tc, node2, contentType, config) and - revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _, config) and - revFlowConsCand(ap2, c, ap1, config) - ) - } - - predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(Ap ap1, Ap ap2 | - revFlow(node2, _, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and - readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _, - pragma[only_bind_into](config)) - ) - } - - additional predicate revFlow(NodeEx node, FlowState state, Configuration config) { - revFlow(node, state, _, _, _, config) - } - - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, _, _, ap, config) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, _, _, _, ap, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, Configuration config) { - revFlow(node, _, _, _, _, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, ap, config) - } - - private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepFwd(_, ap, tc, _, _, config) - } - - private predicate revConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepCand(_, ap, tc, _, _, config) - } - - private predicate validAp(Ap ap, Configuration config) { - revFlow(_, _, _, _, ap, config) and ap instanceof ApNil - or - exists(TypedContent head, Ap tail | - consCand(head, tail, config) and - ap = apCons(head, tail) - ) - } - - additional predicate consCand(TypedContent tc, Ap ap, Configuration config) { - revConsCand(tc, ap, config) and - validAp(ap, config) - } - - pragma[nomagic] - private predicate parameterFlowsThroughRev( - ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp, Configuration config - ) { - revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap, config) and - parameterFlowThroughAllowed(p, pos.getKind()) - } - - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(ReturnPosition pos | - returnFlowsThrough(_, pos, _, _, p, ap, _, config) and - parameterFlowsThroughRev(p, ap, pos, _, config) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - exists(ParamNodeEx p, ReturnPosition pos | - returnFlowsThrough(ret, pos, _, _, p, argAp, ap, config) and - parameterFlowsThroughRev(p, argAp, pos, ap, config) and - kind = pos.getKind() - ) - } - - pragma[nomagic] - private predicate revFlowThroughArg( - DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, - Ap ap, Configuration config - ) { - exists(ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp, config) - ) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | - revFlow(arg, state, returnCtx, returnAp, ap, config) and - revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, config)) and - fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and - states = count(FlowState state | fwdFlow(_, state, _, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | - fwdFlow(n, state, cc, summaryCtx, argAp, ap, config) - ) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, _, _, _, config)) and - fields = count(TypedContent f0 | consCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and - states = count(FlowState state | revFlow(_, state, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | - revFlow(n, state, returnCtx, retAp, ap, config) - ) - } - /* End: Stage logic. */ - } -} - -private module BooleanCallContext { - class Cc extends boolean { - Cc() { this in [true, false] } - } - - class CcCall extends Cc { - CcCall() { this = true } - } - - /** Holds if the call context may be `call`. */ - predicate matchesCall(CcCall cc, DataFlowCall call) { any() } - - class CcNoCall extends Cc { - CcNoCall() { this = false } - } - - Cc ccNone() { result = false } - - CcCall ccSomeCall() { result = true } - - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } -} - -private module Level1CallContext { - class Cc = CallContext; - - class CcCall = CallContextCall; - - pragma[inline] - predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } - - class CcNoCall = CallContextNoCall; - - Cc ccNone() { result instanceof CallContextAny } - - CcCall ccSomeCall() { result instanceof CallContextSomeCall } - - module NoLocalCallContext { - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSiteDispatch(call, c) - then result = TSpecificCall(call) - else result = TSomeCall() - } - } - - module LocalCallContext { - class LocalCc = LocalCallContext; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { - result = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - node.getEnclosingCallable()) - } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() - } - } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { - checkCallContextReturn(innercc, c, call) and - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() - } -} - -private module Stage2Param implements MkStage::StageParam { - private module PrevStage = Stage1; - - class Ap extends boolean { - Ap() { this in [true, false] } - } - - class ApNil extends Ap { - ApNil() { this = false } - } - - bindingset[result, ap] - PrevStage::Ap getApprox(Ap ap) { any() } - - ApNil getApNil(NodeEx node) { Stage1::revFlow(node, _) and exists(result) } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } - - class ApHeadContent = Unit; - - pragma[inline] - ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } - - ApHeadContent projectToHeadContent(Content c) { any() } - - class ApOption = BooleanOption; - - ApOption apNone() { result = TBooleanNone() } - - ApOption apSome(Ap ap) { result = TBooleanSome(ap) } - - import Level1CallContext - import NoLocalCallContext - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - ( - preservesValue = true and - localFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalStateStep(node1, state1, node2, state2, config) - ) and - exists(ap) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand1/6; - - predicate flowIntoCall = flowIntoCallNodeCand1/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - expectsContentEx(node, c) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - PrevStage::revFlowState(state, pragma[only_bind_into](config)) and - exists(ap) and - not stateBarrier(node, state, config) and - ( - notExpectsContent(node) - or - ap = true and - expectsContentCand(node, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage2 implements StageSig { - import MkStage::Stage -} - -pragma[nomagic] -private predicate flowOutOfCallNodeCand2( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -pragma[nomagic] -private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -private module LocalFlowBigStep { - /** - * A node where some checking is required, and hence the big-step relation - * is not allowed to step over. - */ - private class FlowCheckNode extends NodeEx { - FlowCheckNode() { - castNode(this.asNode()) or - clearsContentCached(this.asNode(), _) or - expectsContentCached(this.asNode(), _) - } - } - - /** - * Holds if `node` can be the first node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowEntry(NodeEx node, FlowState state, Configuration config) { - Stage2::revFlow(node, state, config) and - ( - sourceNode(node, state, config) - or - jumpStep(_, node, config) - or - additionalJumpStep(_, node, config) - or - additionalJumpStateStep(_, _, node, state, config) - or - node instanceof ParamNodeEx - or - node.asNode() instanceof OutNodeExt - or - Stage2::storeStepCand(_, _, _, node, _, config) - or - Stage2::readStepCand(_, _, node, config) - or - node instanceof FlowCheckNode - or - exists(FlowState s | - additionalLocalStateStep(_, s, node, state, config) and - s != state - ) - ) - } - - /** - * Holds if `node` can be the last node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowExit(NodeEx node, FlowState state, Configuration config) { - exists(NodeEx next | Stage2::revFlow(next, state, config) | - jumpStep(node, next, config) or - additionalJumpStep(node, next, config) or - flowIntoCallNodeCand2(_, node, next, _, config) or - flowOutOfCallNodeCand2(_, node, _, next, _, config) or - Stage2::storeStepCand(node, _, _, next, _, config) or - Stage2::readStepCand(node, _, next, config) - ) - or - exists(NodeEx next, FlowState s | Stage2::revFlow(next, s, config) | - additionalJumpStateStep(node, state, next, s, config) - or - additionalLocalStateStep(node, state, next, s, config) and - s != state - ) - or - Stage2::revFlow(node, state, config) and - node instanceof FlowCheckNode - or - sinkNode(node, state, config) - } - - pragma[noinline] - private predicate additionalLocalFlowStepNodeCand2( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, Configuration config - ) { - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 and - Stage2::revFlow(node1, pragma[only_bind_into](state1), false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false, - pragma[only_bind_into](config)) - or - additionalLocalStateStep(node1, state1, node2, state2, config) and - Stage2::revFlow(node1, state1, false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, state2, false, pragma[only_bind_into](config)) - } - - /** - * Holds if the local path from `node1` to `node2` is a prefix of a maximal - * subsequence of local flow steps in a dataflow path. - * - * This is the transitive closure of `[additional]localFlowStep` beginning - * at `localFlowEntry`. - */ - pragma[nomagic] - private predicate localFlowStepPlus( - NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, - Configuration config, LocalCallContext cc - ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and - ( - localFlowEntry(node1, pragma[only_bind_into](state), pragma[only_bind_into](config)) and - ( - localFlowStepNodeCand1(node1, node2, config) and - preservesValue = true and - t = node1.getDataFlowType() and // irrelevant dummy value - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - or - additionalLocalFlowStepNodeCand2(node1, state, node2, state, config) and - preservesValue = false and - t = node2.getDataFlowType() - ) and - node1 != node2 and - cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) - or - exists(NodeEx mid | - localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, - pragma[only_bind_into](config), cc) and - localFlowStepNodeCand1(mid, node2, config) and - not mid instanceof FlowCheckNode and - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - ) - or - exists(NodeEx mid | - localFlowStepPlus(node1, state, mid, _, _, pragma[only_bind_into](config), cc) and - additionalLocalFlowStepNodeCand2(mid, state, node2, state, config) and - not mid instanceof FlowCheckNode and - preservesValue = false and - t = node2.getDataFlowType() - ) - ) - } - - /** - * Holds if `node1` can step to `node2` in one or more local steps and this - * path can occur as a maximal subsequence of local steps in a dataflow path. - */ - pragma[nomagic] - predicate localFlowBigStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - DataFlowType t, Configuration config, LocalCallContext callContext - ) { - localFlowStepPlus(node1, state1, node2, preservesValue, t, config, callContext) and - localFlowExit(node2, state1, config) and - state1 = state2 - or - additionalLocalFlowStepNodeCand2(node1, state1, node2, state2, config) and - state1 != state2 and - preservesValue = false and - t = node2.getDataFlowType() and - callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) - } -} - -private import LocalFlowBigStep - -private module Stage3Param implements MkStage::StageParam { - private module PrevStage = Stage2; - - class Ap = ApproxAccessPathFront; - - class ApNil = ApproxAccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } - - class ApHeadContent = ContentApprox; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - predicate projectToHeadContent = getContentApprox/1; - - class ApOption = ApproxAccessPathFrontOption; - - ApOption apNone() { result = TApproxAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } - - import BooleanCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApproxAccessPathFrontNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand2/6; - - predicate flowIntoCall = flowIntoCallNodeCand2/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getAHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage3 implements StageSig { - import MkStage::Stage -} - -private module Stage4Param implements MkStage::StageParam { - private module PrevStage = Stage3; - - class Ap = AccessPathFront; - - class ApNil = AccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathFrontOption; - - ApOption apNone() { result = TAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } - - import BooleanCallContext - - pragma[nomagic] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) and - exists(lcc) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clearSet(NodeEx node, ContentSet c, Configuration config) { - PrevStage::revFlow(node, config) and - clearsContentCached(node.asNode(), c) - } - - pragma[nomagic] - private predicate clearContent(NodeEx node, Content c, Configuration config) { - exists(ContentSet cs | - PrevStage::readStepCand(_, pragma[only_bind_into](c), _, pragma[only_bind_into](config)) and - c = cs.getAReadContent() and - clearSet(node, cs, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clear(NodeEx node, Ap ap, Configuration config) { - clearContent(node, ap.getHead().getContent(), config) - } - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - not clear(node, ap, config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage4 implements StageSig { - import MkStage::Stage -} - -/** - * Holds if `argApf` is recorded as the summary context for flow reaching `node` - * and remains relevant for the following pruning stage. - */ -private predicate flowCandSummaryCtx( - NodeEx node, FlowState state, AccessPathFront argApf, Configuration config -) { - exists(AccessPathFront apf | - Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf, config) and - Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf, - config) - ) -} - -/** - * Holds if a length 2 access path approximation with the head `tc` is expected - * to be expensive. - */ -private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { - exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf, config)) and - nodes = - strictcount(NodeEx n, FlowState state | - Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - or - flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - ) and - accessPathApproxCostLimits(apLimit, tupleLimit) and - apLimit < tails and - tupleLimit < (tails - 1) * nodes and - not tc.forceHighPrecision() - ) -} - -private newtype TAccessPathApprox = - TNil(DataFlowType t) or - TConsNil(TypedContent tc, DataFlowType t) { - Stage4::consCand(tc, TFrontNil(t), _) and - not expensiveLen2unfolding(tc, _) - } or - TConsCons(TypedContent tc1, TypedContent tc2, int len) { - Stage4::consCand(tc1, TFrontHead(tc2), _) and - len in [2 .. accessPathLimit()] and - not expensiveLen2unfolding(tc1, _) - } or - TCons1(TypedContent tc, int len) { - len in [1 .. accessPathLimit()] and - expensiveLen2unfolding(tc, _) - } - -/** - * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only - * the first two elements of the list and its length are tracked. If data flows - * from a source to a given node with a given `AccessPathApprox`, this indicates - * the sequence of dereference operations needed to get from the value in the node - * to the tracked object. The final type indicates the type of the tracked object. - */ -abstract private class AccessPathApprox extends TAccessPathApprox { - abstract string toString(); - - abstract TypedContent getHead(); - - abstract int len(); - - abstract DataFlowType getType(); - - abstract AccessPathFront getFront(); - - /** Gets the access path obtained by popping `head` from this path, if any. */ - abstract AccessPathApprox pop(TypedContent head); -} - -private class AccessPathApproxNil extends AccessPathApprox, TNil { - private DataFlowType t; - - AccessPathApproxNil() { this = TNil(t) } - - override string toString() { result = concat(": " + ppReprType(t)) } - - override TypedContent getHead() { none() } - - override int len() { result = 0 } - - override DataFlowType getType() { result = t } - - override AccessPathFront getFront() { result = TFrontNil(t) } - - override AccessPathApprox pop(TypedContent head) { none() } -} - -abstract private class AccessPathApproxCons extends AccessPathApprox { } - -private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { - private TypedContent tc; - private DataFlowType t; - - AccessPathApproxConsNil() { this = TConsNil(tc, t) } - - override string toString() { - // The `concat` becomes "" if `ppReprType` has no result. - result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) - } - - override TypedContent getHead() { result = tc } - - override int len() { result = 1 } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } -} - -private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { - private TypedContent tc1; - private TypedContent tc2; - private int len; - - AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - - override string toString() { - if len = 2 - then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" - else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc1 } - - override int len() { result = len } - - override DataFlowType getType() { result = tc1.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc1) } - - override AccessPathApprox pop(TypedContent head) { - head = tc1 and - ( - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - } -} - -private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { - private TypedContent tc; - private int len; - - AccessPathApproxCons1() { this = TCons1(tc, len) } - - override string toString() { - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc } - - override int len() { result = len } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { - head = tc and - ( - exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2), _) | - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - or - exists(DataFlowType t | - len = 1 and - Stage4::consCand(tc, TFrontNil(t), _) and - result = TNil(t) - ) - ) - } -} - -/** Gets the access path obtained by popping `tc` from `ap`, if any. */ -private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } - -/** Gets the access path obtained by pushing `tc` onto `ap`. */ -private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } - -private newtype TAccessPathApproxOption = - TAccessPathApproxNone() or - TAccessPathApproxSome(AccessPathApprox apa) - -private class AccessPathApproxOption extends TAccessPathApproxOption { - string toString() { - this = TAccessPathApproxNone() and result = "" - or - this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) - } -} - -private module Stage5Param implements MkStage::StageParam { - private module PrevStage = Stage4; - - class Ap = AccessPathApprox; - - class ApNil = AccessPathApproxNil; - - pragma[nomagic] - PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathApproxOption; - - ApOption apNone() { result = TAccessPathApproxNone() } - - ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } - - import Level1CallContext - import LocalCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, lcc) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { any() } - - // Type checking is not necessary here as it has already been done in stage 3. - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage5 = MkStage::Stage; - -bindingset[conf, result] -private Configuration unbindConf(Configuration conf) { - exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c)) -} - -pragma[nomagic] -private predicate nodeMayUseSummary0( - NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(AccessPathApprox apa0 | - Stage5::parameterMayFlowThrough(p, _, _) and - Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0, config) and - Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), - TAccessPathApproxSome(apa), apa0, config) - ) -} - -pragma[nomagic] -private predicate nodeMayUseSummary( - NodeEx n, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::parameterMayFlowThrough(p, apa, config) and - nodeMayUseSummary0(n, p, state, apa, config) - ) -} - -private newtype TSummaryCtx = - TSummaryCtxNone() or - TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { - exists(Configuration config | - Stage5::parameterMayFlowThrough(p, ap.getApprox(), config) and - Stage5::revFlow(p, state, _, config) - ) - } - -/** - * A context for generating flow summaries. This represents flow entry through - * a specific parameter with an access path of a specific shape. - * - * Summaries are only created for parameters that may flow through. - */ -abstract private class SummaryCtx extends TSummaryCtx { - abstract string toString(); -} - -/** A summary context from which no flow summary can be generated. */ -private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { - override string toString() { result = "" } -} - -/** A summary context from which a flow summary can be generated. */ -private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParamNodeEx p; - private FlowState s; - private AccessPath ap; - - SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } - - ParameterPosition getParameterPos() { p.isParameterOf(_, result) } - - ParamNodeEx getParamNode() { result = p } - - override string toString() { result = p + ": " + ap } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** - * Gets the number of length 2 access path approximations that correspond to `apa`. - */ -private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { - exists(TypedContent tc, int len | - tc = apa.getHead() and - len = apa.len() and - result = - strictcount(AccessPathFront apf | - Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), - config) - ) - ) -} - -private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = - strictcount(NodeEx n, FlowState state | - Stage5::revFlow(n, state, apa, config) or nodeMayUseSummary(n, state, apa, config) - ) -} - -/** - * Holds if a length 2 access path approximation matching `apa` is expected - * to be expensive. - */ -private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = count1to2unfold(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - apLimit < aps and - tupleLimit < (aps - 1) * nodes - ) -} - -private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { - exists(TypedContent head | - apa.pop(head) = result and - Stage5::consCand(head, result, config) - ) -} - -/** - * Holds with `unfold = false` if a precise head-tail representation of `apa` is - * expected to be expensive. Holds with `unfold = true` otherwise. - */ -private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { - if apa.getHead().forceHighPrecision() - then unfold = true - else - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = countPotentialAps(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true - ) -} - -/** - * Gets the number of `AccessPath`s that correspond to `apa`. - */ -pragma[assume_small_delta] -private int countAps(AccessPathApprox apa, Configuration config) { - evalUnfold(apa, false, config) and - result = 1 and - (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) - or - evalUnfold(apa, false, config) and - result = count1to2unfold(apa, config) and - not expensiveLen1to2unfolding(apa, config) - or - evalUnfold(apa, true, config) and - result = countPotentialAps(apa, config) -} - -/** - * Gets the number of `AccessPath`s that would correspond to `apa` assuming - * that it is expanded to a precise head-tail representation. - */ -language[monotonicAggregates] -pragma[assume_small_delta] -private int countPotentialAps(AccessPathApprox apa, Configuration config) { - apa instanceof AccessPathApproxNil and result = 1 - or - result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) -} - -private newtype TAccessPath = - TAccessPathNil(DataFlowType t) or - TAccessPathCons(TypedContent head, AccessPath tail) { - exists(AccessPathApproxCons apa | - not evalUnfold(apa, false, _) and - head = apa.getHead() and - tail.getApprox() = getATail(apa, _) - ) - } or - TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - not expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head1 = apa.getHead() and - head2 = getATail(apa, _).getHead() - ) - } or - TAccessPathCons1(TypedContent head, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head = apa.getHead() - ) - } - -private newtype TPathNode = - pragma[assume_small_delta] - TPathNodeMid( - NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config - ) { - // A PathNode is introduced by a source ... - Stage5::revFlow(node, state, config) and - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - or - // ... or a step from an existing PathNode to another node. - exists(PathNodeMid mid | - pathStep(mid, node, state, cc, sc, ap) and - pragma[only_bind_into](config) = mid.getConfiguration() and - Stage5::revFlow(node, state, ap.getApprox(), pragma[only_bind_into](config)) - ) - } or - TPathNodeSink(NodeEx node, FlowState state, Configuration config) { - exists(PathNodeMid sink | - sink.isAtSink() and - node = sink.getNodeEx() and - state = sink.getState() and - config = sink.getConfiguration() - ) - } or - TPathNodeSourceGroup(string sourceGroup, Configuration config) { - exists(PathNodeImpl source | - sourceGroup = source.getSourceGroup() and - config = source.getConfiguration() - ) - } or - TPathNodeSinkGroup(string sinkGroup, Configuration config) { - exists(PathNodeSink sink | - sinkGroup = sink.getSinkGroup() and - config = sink.getConfiguration() - ) - } - -/** - * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a - * source to a given node with a given `AccessPath`, this indicates the sequence - * of dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ -private class AccessPath extends TAccessPath { - /** Gets the head of this access path, if any. */ - abstract TypedContent getHead(); - - /** Gets the tail of this access path, if any. */ - abstract AccessPath getTail(); - - /** Gets the front of this access path. */ - abstract AccessPathFront getFront(); - - /** Gets the approximation of this access path. */ - abstract AccessPathApprox getApprox(); - - /** Gets the length of this access path. */ - abstract int length(); - - /** Gets a textual representation of this access path. */ - abstract string toString(); - - /** Gets the access path obtained by popping `tc` from this access path, if any. */ - final AccessPath pop(TypedContent tc) { - result = this.getTail() and - tc = this.getHead() - } - - /** Gets the access path obtained by pushing `tc` onto this access path. */ - final AccessPath push(TypedContent tc) { this = result.pop(tc) } -} - -private class AccessPathNil extends AccessPath, TAccessPathNil { - private DataFlowType t; - - AccessPathNil() { this = TAccessPathNil(t) } - - DataFlowType getType() { result = t } - - override TypedContent getHead() { none() } - - override AccessPath getTail() { none() } - - override AccessPathFrontNil getFront() { result = TFrontNil(t) } - - override AccessPathApproxNil getApprox() { result = TNil(t) } - - override int length() { result = 0 } - - override string toString() { result = concat(": " + ppReprType(t)) } -} - -private class AccessPathCons extends AccessPath, TAccessPathCons { - private TypedContent head; - private AccessPath tail; - - AccessPathCons() { this = TAccessPathCons(head, tail) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { result = tail } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - pragma[assume_small_delta] - override AccessPathApproxCons getApprox() { - result = TConsNil(head, tail.(AccessPathNil).getType()) - or - result = TConsCons(head, tail.getHead(), this.length()) - or - result = TCons1(head, this.length()) - } - - pragma[assume_small_delta] - override int length() { result = 1 + tail.length() } - - private string toStringImpl(boolean needsSuffix) { - exists(DataFlowType t | - tail = TAccessPathNil(t) and - needsSuffix = false and - result = head.toString() + "]" + concat(" : " + ppReprType(t)) - ) - or - result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) - or - exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | - result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true - or - result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false - ) - or - exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | - result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true - or - result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false - ) - } - - override string toString() { - result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" - or - result = "[" + this.toStringImpl(false) - } -} - -private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { - private TypedContent head1; - private TypedContent head2; - private int len; - - AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } - - override TypedContent getHead() { result = head1 } - - override AccessPath getTail() { - Stage5::consCand(head1, result.getApprox(), _) and - result.getHead() = head2 and - result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head1) } - - override AccessPathApproxCons getApprox() { - result = TConsCons(head1, head2, len) or - result = TCons1(head1, len) - } - - override int length() { result = len } - - override string toString() { - if len = 2 - then result = "[" + head1.toString() + ", " + head2.toString() + "]" - else - result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" - } -} - -private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { - private TypedContent head; - private int len; - - AccessPathCons1() { this = TAccessPathCons1(head, len) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { - Stage5::consCand(head, result.getApprox(), _) and result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - - override int length() { result = len } - - override string toString() { - if len = 1 - then result = "[" + head.toString() + "]" - else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" - } -} - -abstract private class PathNodeImpl extends TPathNode { - /** Gets the `FlowState` of this node. */ - abstract FlowState getState(); - - /** Gets the associated configuration. */ - abstract Configuration getConfiguration(); - - /** Holds if this node is a source. */ - abstract predicate isSource(); - - abstract PathNodeImpl getASuccessorImpl(); - - private PathNodeImpl getASuccessorIfHidden() { - this.isHidden() and - result = this.getASuccessorImpl() - } - - pragma[nomagic] - private PathNodeImpl getANonHiddenSuccessor0() { - result = this.getASuccessorIfHidden*() and - not result.isHidden() - } - - final PathNodeImpl getANonHiddenSuccessor() { - result = this.getASuccessorImpl().getANonHiddenSuccessor0() and - not this.isHidden() - } - - abstract NodeEx getNodeEx(); - - predicate isHidden() { - not this.getConfiguration().includeHiddenNodes() and - ( - hiddenNode(this.getNodeEx().asNode()) and - not this.isSource() and - not this instanceof PathNodeSink - or - this.getNodeEx() instanceof TNodeImplicitRead - ) - } - - string getSourceGroup() { - this.isSource() and - this.getConfiguration().sourceGrouping(this.getNodeEx().asNode(), result) + not singleConfiguration() and + getConfig(state1).isAdditionalFlowStep(node1, node2) and + state2 = state1 } - predicate isFlowSource() { - this.isSource() and not exists(this.getSourceGroup()) - or - this instanceof PathNodeSourceGroup + predicate allowImplicitRead(Node node, ContentSet c) { + any(Configuration config).allowImplicitRead(node, c) } - predicate isFlowSink() { - this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or - this instanceof PathNodeSinkGroup - } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } - private string ppAp() { - this instanceof PathNodeSink and result = "" - or - exists(string s | s = this.(PathNodeMid).getAp().toString() | - if s = "" then result = "" else result = " " + s - ) - } + FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } - private string ppCtx() { - this instanceof PathNodeSink and result = "" - or - result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + predicate sourceGrouping(Node source, string sourceGroup) { + any(Configuration config).sourceGrouping(source, sourceGroup) } - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + predicate sinkGrouping(Node sink, string sinkGroup) { + any(Configuration config).sinkGrouping(sink, sinkGroup) } -} - -/** Holds if `n` can reach a sink. */ -private predicate directReach(PathNodeImpl n) { - n instanceof PathNodeSink or - n instanceof PathNodeSinkGroup or - directReach(n.getANonHiddenSuccessor()) -} - -/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ -private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } -/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { - n1.getANonHiddenSuccessor() = n2 and directReach(n2) + predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() } } -private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) +private import Impl as I +import I /** * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. */ -class PathNode instanceof PathNodeImpl { - PathNode() { reach(this) } - +class PathNode instanceof I::PathNode { /** Gets a textual representation of this element. */ final string toString() { result = super.toString() } @@ -3406,1548 +358,39 @@ class PathNode instanceof PathNodeImpl { } /** Gets the underlying `Node`. */ - final Node getNode() { super.getNodeEx().projectToNode() = result } + final Node getNode() { result = super.getNode() } /** Gets the `FlowState` of this node. */ - final FlowState getState() { result = super.getState() } + final FlowState getState() { result = getState(super.getState()) } /** Gets the associated configuration. */ - final Configuration getConfiguration() { result = super.getConfiguration() } + final Configuration getConfiguration() { result = getConfig(super.getState()) } /** Gets a successor of this node, if any. */ - final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } + final PathNode getASuccessor() { result = super.getASuccessor() } /** Holds if this node is a source. */ final predicate isSource() { super.isSource() } /** Holds if this node is a grouping of source nodes. */ - final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group, _) } + final predicate isSourceGroup(string group) { super.isSourceGroup(group) } /** Holds if this node is a grouping of sink nodes. */ - final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group, _) } + final predicate isSinkGroup(string group) { super.isSinkGroup(group) } } -/** - * Provides the query predicates needed to include a graph in a path-problem query. - */ -module PathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } - - /** Holds if `n` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode n, string key, string val) { - key = "semmle.label" and val = n.toString() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { - Subpaths::subpaths(arg, par, ret, out) - } -} - -/** - * An intermediate flow graph node. This is a triple consisting of a `Node`, - * a `CallContext`, and a `Configuration`. - */ -private class PathNodeMid extends PathNodeImpl, TPathNodeMid { - NodeEx node; - FlowState state; - CallContext cc; - SummaryCtx sc; - AccessPath ap; - Configuration config; - - PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - SummaryCtx getSummaryCtx() { result = sc } - - AccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - private PathNodeMid getSuccMid() { - pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx(), result.getAp()) and - result.getConfiguration() = unbindConf(this.getConfiguration()) - } - - override PathNodeImpl getASuccessorImpl() { - // an intermediate step to another intermediate node - result = this.getSuccMid() - or - // a final step to a sink - result = this.getSuccMid().projectToSink() - } - - override predicate isSource() { - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - } - - predicate isAtSink() { - sinkNode(node, state, config) and - ap instanceof AccessPathNil and - if hasSinkCallCtx(config) - then - // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` - // is exactly what we need to check. This also implies - // `sc instanceof SummaryCtxNone`. - // For `FeatureEqualSourceSinkCallContext` the initial call context was - // set to `CallContextSomeCall` and jumps are disallowed, so - // `cc instanceof CallContextNoCall` never holds. On the other hand, - // in this case there's never any need to enter a call except to identify - // a summary, so the condition in `pathIntoCallable` enforces this, which - // means that `sc instanceof SummaryCtxNone` holds if and only if we are - // in the call context of the source. - sc instanceof SummaryCtxNone or - cc instanceof CallContextNoCall - else any() - } - - PathNodeSink projectToSink() { - this.isAtSink() and - result.getNodeEx() = node and - result.getState() = state and - result.getConfiguration() = unbindConf(config) - } -} - -/** - * A flow graph node corresponding to a sink. This is disjoint from the - * intermediate nodes in order to uniquely correspond to a given sink by - * excluding the `CallContext`. - */ -private class PathNodeSink extends PathNodeImpl, TPathNodeSink { - NodeEx node; - FlowState state; - Configuration config; - - PathNodeSink() { this = TPathNodeSink(node, state, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result = TPathNodeSinkGroup(this.getSinkGroup(), config) - } - - override predicate isSource() { sourceNode(node, state, config) } - - string getSinkGroup() { config.sinkGrouping(node.asNode(), result) } -} - -private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { - string sourceGroup; - Configuration config; - - PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result.getSourceGroup() = sourceGroup and - result.getConfiguration() = config - } - - override predicate isSource() { none() } - - override string toString() { result = sourceGroup } - - override 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 - } -} - -private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { - string sinkGroup; - Configuration config; - - PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { none() } - - override predicate isSource() { none() } - - override string toString() { result = sinkGroup } - - override 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 - } -} - -private predicate pathNode( - PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, - Configuration conf, LocalCallContext localCC -) { - midnode = mid.getNodeEx() and - state = mid.getState() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - midnode.getEnclosingCallable()) and - ap = mid.getAp() -} - -/** - * Holds if data may flow from `mid` to `node`. The last step in or out of - * a callable is recorded by `cc`. - */ -pragma[assume_small_delta] -pragma[nomagic] -private predicate pathStep( - PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap -) { - exists(NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC | - pathNode(mid, midnode, state0, cc, sc, ap, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, true, _, conf, localCC) - ) - or - exists( - AccessPath ap0, NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC - | - pathNode(mid, midnode, state0, cc, sc, ap0, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), conf, - localCC) and - ap0 instanceof AccessPathNil - ) - or - jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - ap = mid.getAp() - or - additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, mid.getConfiguration()) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - pathIntoCallable(mid, node, state, _, cc, sc, _, _) and ap = mid.getAp() - or - pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone - or - pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() -} - -pragma[nomagic] -private predicate pathReadStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - tc = ap0.getHead() and - Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -pragma[nomagic] -private predicate pathStoreStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -private predicate pathOutOfCallable0( - PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa, - Configuration config -) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - apa = mid.getAp().getApprox() and - config = mid.getConfiguration() -} - -pragma[nomagic] -private predicate pathOutOfCallable1( - PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPathApprox apa, Configuration config -) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - pathOutOfCallable0(mid, pos, state, innercc, apa, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() +private predicate hasFlow(Node source, Node sink, Configuration config) { + exists(PathNode source0, PathNode sink0 | + hasFlowPath(source0, sink0, config) and + source0.getNode() = source and + sink0.getNode() = sink ) } -pragma[noinline] -private NodeEx getAnOutNodeFlow( - ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config -) { - result.asNode() = kind.getAnOutNode(call) and - Stage5::revFlow(result, _, apa, config) +private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) { + hasFlowPath(source, sink) and source.getConfiguration() = config } -/** - * Holds if data may flow from `mid` to `out`. The last step of this path - * is a return from a callable and is recorded by `cc`, if needed. - */ -pragma[noinline] -private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { - exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | - pathOutOfCallable1(mid, call, kind, state, cc, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} +private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) } -/** - * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. - */ -pragma[noinline] -private predicate pathIntoArg( - PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(ArgNodeEx arg, ArgumentPosition apos | - pathNode(mid, arg, state, cc, _, ap, config, _) and - arg.asNode().(ArgNode).argumentOf(call, apos) and - apa = ap.getApprox() and - parameterMatch(ppos, apos) - ) -} - -pragma[nomagic] -private predicate parameterCand( - DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::revFlow(p, _, apa, config) and - p.isParameterOf(callable, pos) - ) -} - -pragma[nomagic] -private predicate pathIntoCallable0( - PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, AccessPath ap, Configuration config -) { - exists(AccessPathApprox apa | - pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - callable = resolveCall(call, outercc) and - parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa), - pragma[only_bind_into](config)) - ) -} - -/** - * Holds if data may flow from `mid` to `p` through `call`. The contexts - * before and after entering the callable are `outercc` and `innercc`, - * respectively. - */ -pragma[nomagic] -private predicate pathIntoCallable( - PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, - SummaryCtx sc, DataFlowCall call, Configuration config -) { - exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | - pathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - ( - sc = TSummaryCtxSome(p, state, ap) - or - not exists(TSummaryCtxSome(p, state, ap)) and - sc = TSummaryCtxNone() and - // When the call contexts of source and sink needs to match then there's - // never any reason to enter a callable except to find a summary. See also - // the comment in `PathNodeMid::isAtSink`. - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) -} - -/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ -pragma[nomagic] -private predicate paramFlowsThrough( - ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, - AccessPathApprox apa, Configuration config -) { - exists(RetNodeEx ret | - pathNode(_, ret, state, cc, sc, ap, config, _) and - kind = ret.getKind() and - apa = ap.getApprox() and - parameterFlowThroughAllowed(sc.getParamNode(), kind) - ) -} - -pragma[nomagic] -private predicate pathThroughCallable0( - DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(CallContext innercc, SummaryCtx sc | - pathIntoCallable(mid, _, _, cc, innercc, sc, call, config) and - paramFlowsThrough(kind, state, innercc, sc, ap, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` through a callable to the node `out`. - * The context `cc` is restored to its value prior to entering the callable. - */ -pragma[noinline] -private predicate pathThroughCallable( - PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap -) { - exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config | - pathThroughCallable0(call, mid, kind, state, cc, ap, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -private module Subpaths { - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths01( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - exists(Configuration config | - pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and - pathIntoCallable(arg, par, _, _, innercc, sc, _, config) and - paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, - pragma[only_bind_into](apout), _, unbindConf(config)) and - not arg.isHidden() - ) - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `sout`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths02( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and - out.asNode() = kind.getAnOutNode(_) - } - - pragma[nomagic] - private Configuration getPathNodeConf(PathNodeImpl n) { result = n.getConfiguration() } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple. - */ - pragma[nomagic] - private predicate subpaths03( - PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, AccessPath apout - ) { - exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | - subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and - pathNode(ret, retnode, sout, innercc, sc, apout, unbindConf(getPathNodeConf(arg)), _) and - kind = retnode.getKind() - ) - } - - private PathNodeImpl localStepToHidden(PathNodeImpl n) { - n.getASuccessorImpl() = result and - result.isHidden() and - exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | - localFlowBigStep(n1, _, n2, _, _, _, _, _) or - store(n1, _, n2, _, _) or - readSet(n1, _, n2, _) - ) - } - - pragma[nomagic] - private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { - succ = pred.getANonHiddenSuccessor() and - succNode = succ.getNodeEx() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { - exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | - pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and - subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and - hasSuccessor(pragma[only_bind_into](arg), par, p) and - not ret.isHidden() and - pathNode(out0, o, sout, _, _, apout, _, _) - | - out = out0 or out = out0.projectToSink() - ) - } - - /** - * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. - */ - predicate retReach(PathNodeImpl n) { - exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) - or - exists(PathNodeImpl mid | - retReach(mid) and - n.getANonHiddenSuccessor() = mid and - not subpaths(_, mid, _, _) - ) - } -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -private predicate hasFlowPath( - PathNodeImpl flowsource, PathNodeImpl flowsink, Configuration configuration -) { - flowsource.isFlowSource() and - flowsource.getConfiguration() = configuration and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.isFlowSink() -} - -private predicate flowsTo( - PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink, - Configuration configuration -) { - flowsource.isSource() and - flowsource.getConfiguration() = configuration and - flowsource.getNodeEx().asNode() = source and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.getNodeEx().asNode() = sink -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -predicate flowsTo(Node source, Node sink, Configuration configuration) { - flowsTo(_, _, source, sink, configuration) -} - -private predicate finalStats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples -) { - fwd = true and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and - tuples = count(PathNodeImpl pn) - or - fwd = false and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and - tuples = count(PathNode pn) -} - -/** - * INTERNAL: Only for debugging. - * - * Calculates per-stage metrics for data flow. - */ -predicate stageStats( - int n, string stage, int nodes, int fields, int conscand, int states, int tuples, - Configuration config -) { - stage = "1 Fwd" and - n = 10 and - Stage1::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "1 Rev" and - n = 15 and - Stage1::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Fwd" and - n = 20 and - Stage2::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Rev" and - n = 25 and - Stage2::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Fwd" and - n = 30 and - Stage3::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Rev" and - n = 35 and - Stage3::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Fwd" and - n = 40 and - Stage4::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Rev" and - n = 45 and - Stage4::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Fwd" and - n = 50 and - Stage5::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Rev" and - n = 55 and - Stage5::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) - or - stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) -} - -private module FlowExploration { - private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - jumpStep(node1, node2, config) - or - additionalJumpStep(node1, node2, config) - or - additionalJumpStateStep(node1, _, node2, _, config) - or - // flow into callable - viableParamArgEx(_, node2, node1) - or - // flow out of a callable - viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) - | - c1 = node1.getEnclosingCallable() and - c2 = node2.getEnclosingCallable() and - c1 != c2 - ) - } - - private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSource(n) or config.isSource(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSrc(mid, config) and callableStep(mid, c, config) - ) - } - - private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSink(n) or config.isSink(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSink(mid, config) and callableStep(c, mid, config) - ) - } - - private newtype TCallableExt = - TCallable(DataFlowCallable c, Configuration config) { - interestingCallableSrc(c, config) or - interestingCallableSink(c, config) - } or - TCallableSrc() or - TCallableSink() - - private predicate callableExtSrc(TCallableSrc src) { any() } - - private predicate callableExtSink(TCallableSink sink) { any() } - - private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { - exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | - callableStep(c1, c2, config) and - ce1 = TCallable(c1, pragma[only_bind_into](config)) and - ce2 = TCallable(c2, pragma[only_bind_into](config)) - ) - or - exists(Node n, Configuration config | - ce1 = TCallableSrc() and - (config.isSource(n) or config.isSource(n, _)) and - ce2 = TCallable(getNodeEnclosingCallable(n), config) - ) - or - exists(Node n, Configuration config | - ce2 = TCallableSink() and - (config.isSink(n) or config.isSink(n, _)) and - ce1 = TCallable(getNodeEnclosingCallable(n), config) - ) - } - - private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { - callableExtStepFwd(ce2, ce1) - } - - private int distSrcExt(TCallableExt c) = - shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) - - private int distSinkExt(TCallableExt c) = - shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) - - private int distSrc(DataFlowCallable c, Configuration config) { - result = distSrcExt(TCallable(c, config)) - 1 - } - - private int distSink(DataFlowCallable c, Configuration config) { - result = distSinkExt(TCallable(c, config)) - 1 - } - - private newtype TPartialAccessPath = - TPartialNil(DataFlowType t) or - TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first - * element of the list and its length are tracked. If data flows from a source to - * a given node with a given `AccessPath`, this indicates the sequence of - * dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ - private class PartialAccessPath extends TPartialAccessPath { - abstract string toString(); - - TypedContent getHead() { this = TPartialCons(result, _) } - - int len() { - this = TPartialNil(_) and result = 0 - or - this = TPartialCons(_, result) - } - - DataFlowType getType() { - this = TPartialNil(result) - or - exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) - } - } - - private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { - override string toString() { - exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) - } - } - - private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { - override string toString() { - exists(TypedContent tc, int len | this = TPartialCons(tc, len) | - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private newtype TRevPartialAccessPath = - TRevPartialNil() or - TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `Content`s, but only the first - * element of the list and its length are tracked. - */ - private class RevPartialAccessPath extends TRevPartialAccessPath { - abstract string toString(); - - Content getHead() { this = TRevPartialCons(result, _) } - - int len() { - this = TRevPartialNil() and result = 0 - or - this = TRevPartialCons(_, result) - } - } - - private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { - override string toString() { result = "" } - } - - private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { - override string toString() { - exists(Content c, int len | this = TRevPartialCons(c, len) | - if len = 1 - then result = "[" + c.toString() + "]" - else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private predicate relevantState(FlowState state) { - sourceNode(_, state, _) or - sinkNode(_, state, _) or - additionalLocalStateStep(_, state, _, _, _) or - additionalLocalStateStep(_, _, _, state, _) or - additionalJumpStateStep(_, state, _, _, _) or - additionalJumpStateStep(_, _, _, state, _) - } - - private newtype TSummaryCtx1 = - TSummaryCtx1None() or - TSummaryCtx1Param(ParamNodeEx p) - - private newtype TSummaryCtx2 = - TSummaryCtx2None() or - TSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TSummaryCtx3 = - TSummaryCtx3None() or - TSummaryCtx3Some(PartialAccessPath ap) - - private newtype TRevSummaryCtx1 = - TRevSummaryCtx1None() or - TRevSummaryCtx1Some(ReturnPosition pos) - - private newtype TRevSummaryCtx2 = - TRevSummaryCtx2None() or - TRevSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TRevSummaryCtx3 = - TRevSummaryCtx3None() or - TRevSummaryCtx3Some(RevPartialAccessPath ap) - - private newtype TPartialPathNode = - TPartialPathNodeFwd( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = TPartialNil(node.getDataFlowType()) and - exists(config.explorationLimit()) - or - partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap, config) and - distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() - } or - TPartialPathNodeRev( - NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, - RevPartialAccessPath ap, Configuration config - ) { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() and - exists(config.explorationLimit()) - or - revPartialPathStep(_, node, state, sc1, sc2, sc3, ap, config) and - not clearsContentEx(node, ap.getHead()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead()) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() - } - - pragma[nomagic] - private predicate partialPathNodeMk0( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap, config) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - not clearsContentEx(node, ap.getHead().getContent()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead().getContent()) - ) and - if node.asNode() instanceof CastingNode - then compatibleTypes(node.getDataFlowType(), ap.getType()) - else any() - } - - /** - * A `Node` augmented with a call context, an access path, and a configuration. - */ - class PartialPathNode extends TPartialPathNode { - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() - } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - /** Gets the underlying `Node`. */ - final Node getNode() { this.getNodeEx().projectToNode() = result } - - FlowState getState() { none() } - - private NodeEx getNodeEx() { - result = this.(PartialPathNodeFwd).getNodeEx() or - result = this.(PartialPathNodeRev).getNodeEx() - } - - /** Gets the associated configuration. */ - Configuration getConfiguration() { none() } - - /** Gets a successor of this node, if any. */ - PartialPathNode getASuccessor() { none() } - - /** - * Gets the approximate distance to the nearest source measured in number - * of interprocedural steps. - */ - int getSourceDistance() { - result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - /** - * Gets the approximate distance to the nearest sink measured in number - * of interprocedural steps. - */ - int getSinkDistance() { - result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - private string ppAp() { - exists(string s | - s = this.(PartialPathNodeFwd).getAp().toString() or - s = this.(PartialPathNodeRev).getAp().toString() - | - if s = "" then result = "" else result = " " + s - ) - } - - private string ppCtx() { - result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" - } - - /** Holds if this is a source in a forward-flow path. */ - predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } - - /** Holds if this is a sink in a reverse-flow path. */ - predicate isRevSink() { this.(PartialPathNodeRev).isSink() } - } - - /** - * Provides the query predicates needed to include a graph in a path-problem query. - */ - module PartialPathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } - } - - private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { - NodeEx node; - FlowState state; - CallContext cc; - TSummaryCtx1 sc1; - TSummaryCtx2 sc2; - TSummaryCtx3 sc3; - PartialAccessPath ap; - Configuration config; - - PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - TSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TSummaryCtx3 getSummaryCtx3() { result = sc3 } - - PartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeFwd getASuccessor() { - partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp(), - result.getConfiguration()) - } - - predicate isSource() { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap instanceof TPartialNil - } - } - - private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { - NodeEx node; - FlowState state; - TRevSummaryCtx1 sc1; - TRevSummaryCtx2 sc2; - TRevSummaryCtx3 sc3; - RevPartialAccessPath ap; - Configuration config; - - PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } - - RevPartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeRev getASuccessor() { - revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), - this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp(), this.getConfiguration()) - } - - predicate isSink() { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() - } - } - - private predicate partialPathStep( - PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, - TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and - ( - localFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - ) - or - jumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - partialPathStoreStep(mid, _, _, node, ap) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(PartialAccessPath ap0, TypedContent tc | - partialPathReadStep(mid, ap0, tc, node, cc, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsFwd(ap, tc, ap0, config) - ) - or - partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap, config) - or - partialPathOutOfCallable(mid, node, state, cc, ap, config) and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() - or - partialPathThroughCallable(mid, node, state, cc, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - bindingset[result, i] - private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } - - pragma[inline] - private predicate partialPathStoreStep( - PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, - PartialAccessPath ap2 - ) { - exists(NodeEx midNode, DataFlowType contentType | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - store(midNode, tc, node, contentType, mid.getConfiguration()) and - ap2.getHead() = tc and - ap2.len() = unbindInt(ap1.len() + 1) and - compatibleTypes(ap1.getType(), contentType) - ) - } - - pragma[nomagic] - private predicate apConsFwd( - PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeFwd mid | - partialPathStoreStep(mid, ap1, tc, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate partialPathReadStep( - PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc, - Configuration config - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and - ap.getHead() = tc and - pragma[only_bind_into](config) = mid.getConfiguration() and - cc = mid.getCallContext() - ) - } - - private predicate partialPathOutOfCallable0( - PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, - PartialAccessPath ap, Configuration config - ) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - ap = mid.getAp() and - config = mid.getConfiguration() - } - - pragma[nomagic] - private predicate partialPathOutOfCallable1( - PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - partialPathOutOfCallable0(mid, pos, state, innercc, ap, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) - } - - private predicate partialPathOutOfCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(ReturnKindExt kind, DataFlowCall call | - partialPathOutOfCallable1(mid, call, kind, state, cc, ap, config) - | - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[noinline] - private predicate partialPathIntoArg( - PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ArgNode arg, ArgumentPosition apos | - arg = mid.getNodeEx().asNode() and - state = mid.getState() and - cc = mid.getCallContext() and - arg.argumentOf(call, apos) and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate partialPathIntoCallable0( - PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - partialPathIntoArg(mid, pos, state, outercc, call, ap, config) and - callable = resolveCall(call, outercc) - } - - private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, - CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ParameterPosition pos, DataFlowCallable callable | - partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - sc1 = TSummaryCtx1Param(p) and - sc2 = TSummaryCtx2Some(state) and - sc3 = TSummaryCtx3Some(ap) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) - } - - pragma[nomagic] - private predicate paramFlowsThroughInPartialPath( - ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeFwd mid, RetNodeEx ret | - mid.getNodeEx() = ret and - kind = ret.getKind() and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() and - ap = mid.getAp() - ) - } - - pragma[noinline] - private predicate partialPathThroughCallable0( - DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | - partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _, config) and - paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap, config) - ) - } - - private predicate partialPathThroughCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ReturnKindExt kind | - partialPathThroughCallable0(call, mid, kind, state, cc, ap, config) and - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[nomagic] - private predicate revPartialPathStep( - PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, - TRevSummaryCtx3 sc3, RevPartialAccessPath ap, Configuration config - ) { - localFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - jumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - revPartialPathReadStep(mid, _, _, node, ap) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(RevPartialAccessPath ap0, Content c | - revPartialPathStoreStep(mid, ap0, c, node, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsRev(ap, c, ap0, config) - ) - or - exists(ParamNodeEx p | - mid.getNodeEx() = p and - viableParamArgEx(_, p, node) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - or - exists(ReturnPosition pos | - revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap, config) and - pos = getReturnPosition(node.asNode()) - ) - or - revPartialPathThroughCallable(mid, node, state, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - pragma[inline] - private predicate revPartialPathReadStep( - PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, - RevPartialAccessPath ap2 - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - read(node, c, midNode, mid.getConfiguration()) and - ap2.getHead() = c and - ap2.len() = unbindInt(ap1.len() + 1) - ) - } - - pragma[nomagic] - private predicate apConsRev( - RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeRev mid | - revPartialPathReadStep(mid, ap1, c, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathStoreStep( - PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config - ) { - exists(NodeEx midNode, TypedContent tc | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - store(node, tc, midNode, _, config) and - ap.getHead() = c and - config = mid.getConfiguration() and - tc.getContent() = c - ) - } - - pragma[nomagic] - private predicate revPartialPathIntoReturn( - PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, - TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap, - Configuration config - ) { - exists(NodeEx out | - mid.getNodeEx() = out and - mid.getState() = state and - viableReturnPosOutEx(call, pos, out) and - sc1 = TRevSummaryCtx1Some(pos) and - sc2 = TRevSummaryCtx2Some(state) and - sc3 = TRevSummaryCtx3Some(ap) and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathFlowsThrough( - ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, - TRevSummaryCtx3Some sc3, RevPartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | - mid.getNodeEx() = p and - mid.getState() = state and - p.getPosition() = ppos and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable0( - DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, - RevPartialAccessPath ap, Configuration config - ) { - exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | - revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _, config) and - revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap, config) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ArgumentPosition pos | - revPartialPathThroughCallable0(call, mid, pos, state, ap, config) and - node.asNode().(ArgNode).argumentOf(call, pos) - ) - } -} - -import FlowExploration - -private predicate partialFlow( - PartialPathNode source, PartialPathNode node, Configuration configuration -) { - source.getConfiguration() = configuration and - source.isFwdSource() and - node = source.getASuccessor+() -} - -private predicate revPartialFlow( - PartialPathNode node, PartialPathNode sink, Configuration configuration -) { - sink.getConfiguration() = configuration and - sink.isRevSink() and - node.getASuccessor+() = sink -} +predicate flowsTo = hasFlow/3; diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl6.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl6.qll index 1b969756b09..e6bdc74cceb 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl6.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl6.qll @@ -1,16 +1,16 @@ /** - * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from - * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed - * through the `Configuration` class. This file exists in several identical - * copies, allowing queries to use multiple `Configuration` classes that depend - * on each other without introducing mutual recursion among those configurations. + * DEPRECATED: Use `Make` and `MakeWithState` instead. + * + * Provides a `Configuration` class backwards-compatible interface to the data + * flow library. */ private import DataFlowImplCommon private import DataFlowImplSpecific::Private import DataFlowImplSpecific::Public +private import DataFlowImpl import DataFlowImplCommonPublic +import FlowStateString /** * A configuration of interprocedural data flow analysis. This defines @@ -144,6 +144,10 @@ abstract class Configuration extends string { * - `FeatureEqualSourceSinkCallContext`: * Implies both of the above and additionally ensures that the entire flow * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. */ FlowFeature getAFeature() { none() } @@ -156,7 +160,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from `source` to `sink` for this configuration. */ - predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } + predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) } /** * Holds if data may flow from `source` to `sink` for this configuration. @@ -169,9 +173,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from some source to `sink` for this configuration. */ - predicate hasFlowTo(Node sink) { - sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode() - } + predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) } /** * Holds if data may flow from some source to `sink` for this configuration. @@ -179,10 +181,12 @@ abstract class Configuration extends string { predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } /** + * DEPRECATED: Use `FlowExploration` instead. + * * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` * measured in approximate number of interprocedural steps. */ - int explorationLimit() { none() } + deprecated int explorationLimit() { none() } /** * Holds if hidden nodes should be included in the data flow graph. @@ -191,49 +195,6 @@ abstract class Configuration extends string { * is not visualized (for example in a `path-problem` query). */ predicate includeHiddenNodes() { none() } - - /** - * Holds if there is a partial data flow path from `source` to `node`. The - * approximate distance between `node` and the closest source is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards sink definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sources is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - */ - final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { - partialFlow(source, node, this) and - dist = node.getSourceDistance() - } - - /** - * Holds if there is a partial data flow path from `node` to `sink`. The - * approximate distance between `node` and the closest sink is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards source definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sinks is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - * - * Note that reverse flow has slightly lower precision than the corresponding - * forward flow, as reverse flow disregards type pruning among other features. - */ - final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { - revPartialFlow(node, sink, this) and - dist = node.getSinkDistance() - } } /** @@ -263,90 +224,6 @@ abstract private class ConfigurationRecursionPrevention extends Configuration { } } -private newtype TNodeEx = - TNodeNormal(Node n) or - TNodeImplicitRead(Node n, boolean hasRead) { - any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true] - } - -private class NodeEx extends TNodeEx { - string toString() { - result = this.asNode().toString() - or - exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") - } - - Node asNode() { this = TNodeNormal(result) } - - predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } - - Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } - - pragma[nomagic] - private DataFlowCallable getEnclosingCallable0() { - nodeEnclosingCallable(this.projectToNode(), result) - } - - pragma[inline] - DataFlowCallable getEnclosingCallable() { - pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) - } - - pragma[nomagic] - private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } - - pragma[inline] - DataFlowType getDataFlowType() { - pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) - } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -private class ArgNodeEx extends NodeEx { - ArgNodeEx() { this.asNode() instanceof ArgNode } -} - -private class ParamNodeEx extends NodeEx { - ParamNodeEx() { this.asNode() instanceof ParamNode } - - predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { - this.asNode().(ParamNode).isParameterOf(c, pos) - } - - ParameterPosition getPosition() { this.isParameterOf(_, result) } -} - -private class RetNodeEx extends NodeEx { - RetNodeEx() { this.asNode() instanceof ReturnNodeExt } - - ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } - - ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } -} - -private predicate inBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierIn(n) - | - config.isSource(n) or config.isSource(n, _) - ) -} - -private predicate outBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierOut(n) - | - config.isSink(n) or config.isSink(n, _) - ) -} - /** A bridge class to access the deprecated `isBarrierGuard`. */ private class BarrierGuardGuardedNodeBridge extends Unit { abstract predicate guardedNode(Node n, Configuration config); @@ -370,3019 +247,94 @@ private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { } } -pragma[nomagic] -private predicate fullBarrier(NodeEx node, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n) +private FlowState relevantState(Configuration config) { + config.isSource(_, result) or + config.isSink(_, result) or + config.isBarrier(_, result) or + config.isAdditionalFlowStep(_, result, _, _) or + config.isAdditionalFlowStep(_, _, _, result) +} + +private newtype TConfigState = + TMkConfigState(Configuration config, FlowState state) { + state = relevantState(config) or state instanceof FlowStateEmpty + } + +private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) } + +private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) } + +private predicate singleConfiguration() { 1 = strictcount(Configuration c) } + +private module Config implements FullStateConfigSig { + class FlowState = TConfigState; + + predicate isSource(Node source, FlowState state) { + getConfig(state).isSource(source, getState(state)) or - config.isBarrierIn(n) and - not config.isSource(n) and - not config.isSource(n, _) + getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty + } + + predicate isSink(Node sink, FlowState state) { + getConfig(state).isSink(sink, getState(state)) or - config.isBarrierOut(n) and - not config.isSink(n) and - not config.isSink(n, _) + getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty + } + + predicate isBarrier(Node node) { none() } + + predicate isBarrier(Node node, FlowState state) { + getConfig(state).isBarrier(node, getState(state)) or + getConfig(state).isBarrier(node) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state)) + } + + predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) } + + predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) } + + predicate isAdditionalFlowStep(Node node1, Node node2) { + singleConfiguration() and + any(Configuration config).isAdditionalFlowStep(node1, node2) + } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and + getConfig(state2) = getConfig(state1) or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config) - ) -} - -pragma[nomagic] -private predicate stateBarrier(NodeEx node, FlowState state, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n, state) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config) - ) -} - -pragma[nomagic] -private predicate sourceNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSource(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSource(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -pragma[nomagic] -private predicate sinkNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSink(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSink(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -/** Provides the relevant barriers for a step from `node1` to `node2`. */ -pragma[inline] -private predicate stepFilter(NodeEx node1, NodeEx node2, Configuration config) { - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) -} - -/** - * Holds if data can flow in one local step from `node1` to `node2`. - */ -private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.asNode() = n and - node2.isImplicitReadNode(n, false) and - not fullBarrier(node1, config) - ) -} - -/** - * Holds if the additional step from `node1` to `node2` does not jump between callables. - */ -private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.isImplicitReadNode(n, true) and - node2.asNode() = n and - not fullBarrier(node2, config) - ) -} - -private predicate additionalLocalStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) - ) -} - -/** - * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. - */ -private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if the additional step from `node1` to `node2` jumps between callables. - */ -private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate additionalJumpStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -pragma[nomagic] -private predicate readSet(NodeEx node1, ContentSet c, NodeEx node2, Configuration config) { - readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and - stepFilter(node1, node2, config) - or - exists(Node n | - node2.isImplicitReadNode(n, true) and - node1.isImplicitReadNode(n, _) and - config.allowImplicitRead(n, c) - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(ContentSet cs | - readSet(node1, cs, node2, config) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate clearsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - clearsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate expectsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - expectsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -pragma[nomagic] -private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } - -pragma[nomagic] -private predicate hasReadStep(Content c, Configuration config) { read(_, c, _, config) } - -pragma[nomagic] -private predicate store( - NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config -) { - store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), - contentType) and - hasReadStep(tc.getContent(), config) and - stepFilter(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { - viableReturnPosOut(call, pos, out.asNode()) -} - -pragma[nomagic] -private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { - viableParamArg(call, p.asNode(), arg.asNode()) -} - -/** - * Holds if field flow should be used for the given configuration. - */ -private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } - -private predicate hasSourceCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSourceCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate hasSinkCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSinkCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if flow from `p` to a return node of kind `kind` is allowed. - * - * We don't expect a parameter to return stored in itself, unless - * explicitly allowed - */ -bindingset[p, kind] -private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { - exists(ParameterPosition pos | p.isParameterOf(_, pos) | - not kind.(ParamUpdateReturnKind).getPosition() = pos - or - allowParameterReturnInSelfCached(p.asNode()) - ) -} - -private module Stage1 implements StageSig { - class Ap extends int { - // workaround for bad functionality-induced joins (happens when using `Unit`) - pragma[nomagic] - Ap() { this in [0 .. 1] and this < 1 } - } - - private class Cc = boolean; - - /* Begin: Stage 1 logic. */ - /** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `cc` records whether the node is reached through an - * argument in a call. - */ - private predicate fwdFlow(NodeEx node, Cc cc, Configuration config) { - sourceNode(node, _, config) and - if hasSourceCallCtx(config) then cc = true else cc = false - or - exists(NodeEx mid | fwdFlow(mid, cc, config) | - localFlowStep(mid, node, config) or - additionalLocalFlowStep(mid, node, config) or - additionalLocalStateStep(mid, _, node, _, config) - ) - or - exists(NodeEx mid | fwdFlow(mid, _, config) and cc = false | - jumpStep(mid, node, config) or - additionalJumpStep(mid, node, config) or - additionalJumpStateStep(mid, _, node, _, config) - ) - or - // store - exists(NodeEx mid | - useFieldFlow(config) and - fwdFlow(mid, cc, config) and - store(mid, _, node, _, config) - ) - or - // read - exists(ContentSet c | - fwdFlowReadSet(c, node, cc, config) and - fwdFlowConsCandSet(c, _, config) - ) - or - // flow into a callable - fwdFlowIn(_, _, _, node, config) and - cc = true - or - // flow out of a callable - fwdFlowOut(_, node, false, config) and - cc = false - or - // flow through a callable - exists(DataFlowCall call | - fwdFlowOutFromArg(call, node, config) and - fwdFlowIsEntered(call, cc, config) - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowIn( - DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p, Configuration config - ) { - // call context cannot help reduce virtual dispatch - fwdFlow(arg, cc, config) and - viableParamArgEx(call, p, arg) and - not fullBarrier(p, config) and - ( - cc = false - or - cc = true and - not reducedViableImplInCallContext(call, _, _) - ) - or - // call context may help reduce virtual dispatch - exists(DataFlowCallable target | - fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target, config) and - target = viableImplInSomeFwdFlowCallContextExt(call, config) and - cc = true - ) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - fwdFlowIn(call, _, cc, _, config) - } - - pragma[nomagic] - private predicate fwdFlowInReducedViableImplInSomeCallContext( - DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target, Configuration config - ) { - fwdFlow(arg, true, config) and - viableParamArgEx(call, p, arg) and - reducedViableImplInCallContext(call, _, _) and - target = p.getEnclosingCallable() and - not fullBarrier(p, config) - } - - /** - * Gets a viable dispatch target of `call` in the context `ctx`. This is - * restricted to those `call`s for which a context might make a difference, - * and to `ctx`s that are reachable in `fwdFlow`. - */ - pragma[nomagic] - private DataFlowCallable viableImplInSomeFwdFlowCallContextExt( - DataFlowCall call, Configuration config - ) { - exists(DataFlowCall ctx | - fwdFlowIsEntered(ctx, _, config) and - result = viableImplInCallContextExt(call, ctx) - ) - } - - private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) } - - pragma[nomagic] - private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc, Configuration config) { - exists(NodeEx mid | - fwdFlow(mid, cc, config) and - readSet(mid, c, node, config) - ) - } - - /** - * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - fwdFlow(mid, _, config) and - store(mid, tc, node, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `cs` may be interpreted in a read as the target of some store - * into `c`, in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCandSet(ContentSet cs, Content c, Configuration config) { - fwdFlowConsCand(c, config) and - c = cs.getAReadContent() - } - - pragma[nomagic] - private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { - exists(RetNodeEx ret | - fwdFlow(ret, cc, config) and - ret.getReturnPosition() = pos - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) { - exists(ReturnPosition pos | - fwdFlowReturnPosition(pos, cc, config) and - viableReturnPosOutEx(call, pos, out) and - not fullBarrier(out, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) { - fwdFlowOut(call, out, true, config) - } - - private predicate stateStepFwd(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1 | - additionalLocalStateStep(node1, state1, _, state2, config) or - additionalJumpStateStep(node1, state1, _, state2, config) - | - fwdFlow(node1, config) - ) - } - - private predicate fwdFlowState(FlowState state, Configuration config) { - sourceNode(_, state, config) - or - exists(FlowState state0 | - fwdFlowState(state0, config) and - stateStepFwd(state0, state, config) - ) - } - - /** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink. - */ - pragma[nomagic] - private predicate revFlow(NodeEx node, boolean toReturn, Configuration config) { - revFlow0(node, toReturn, config) and - fwdFlow(node, config) - } - - pragma[nomagic] - private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) { - exists(FlowState state | - fwdFlow(node, pragma[only_bind_into](config)) and - sinkNode(node, state, config) and - fwdFlowState(state, pragma[only_bind_into](config)) and - if hasSinkCallCtx(config) then toReturn = true else toReturn = false - ) - or - exists(NodeEx mid | revFlow(mid, toReturn, config) | - localFlowStep(node, mid, config) or - additionalLocalFlowStep(node, mid, config) or - additionalLocalStateStep(node, _, mid, _, config) - ) - or - exists(NodeEx mid | revFlow(mid, _, config) and toReturn = false | - jumpStep(node, mid, config) or - additionalJumpStep(node, mid, config) or - additionalJumpStateStep(node, _, mid, _, config) - ) - or - // store - exists(Content c | - revFlowStore(c, node, toReturn, config) and - revFlowConsCand(c, config) - ) - or - // read - exists(NodeEx mid, ContentSet c | - readSet(node, c, mid, config) and - fwdFlowConsCandSet(c, _, pragma[only_bind_into](config)) and - revFlow(mid, toReturn, pragma[only_bind_into](config)) - ) - or - // flow into a callable - revFlowIn(_, node, false, config) and - toReturn = false - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(pos, config) and - node.(RetNodeEx).getReturnPosition() = pos and - toReturn = true - ) - or - // flow through a callable - exists(DataFlowCall call | - revFlowInToReturn(call, node, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - /** - * Holds if `c` is the target of a read in the flow covered by `revFlow`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, ContentSet cs | - fwdFlow(node, pragma[only_bind_into](config)) and - readSet(node, cs, mid, config) and - fwdFlowConsCandSet(cs, c, pragma[only_bind_into](config)) and - revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) { - exists(NodeEx mid, TypedContent tc | - revFlow(mid, toReturn, pragma[only_bind_into](config)) and - fwdFlowConsCand(c, pragma[only_bind_into](config)) and - store(node, tc, mid, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `c` is the target of both a read and a store in the flow covered - * by `revFlow`. - */ - pragma[nomagic] - additional predicate revFlowIsReadAndStored(Content c, Configuration conf) { - revFlowConsCand(c, conf) and - revFlowStore(c, _, _, conf) - } - - pragma[nomagic] - additional predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config - ) { - fwdFlowReturnPosition(pos, _, config) and - viableReturnPosOutEx(call, pos, out) - } - - pragma[nomagic] - private predicate revFlowOut(ReturnPosition pos, Configuration config) { - exists(NodeEx out | - revFlow(out, _, config) and - viableReturnPosOutNodeCandFwd1(_, pos, out, config) - ) - } - - pragma[nomagic] - additional predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config - ) { - fwdFlowIn(call, arg, _, p, config) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate revFlowIn( - DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config - ) { - exists(ParamNodeEx p | - revFlow(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) { - revFlowIn(call, arg, true, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(NodeEx out | - revFlow(out, toReturn, config) and - fwdFlowOutFromArg(call, out, config) - ) - } - - private predicate stateStepRev(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - additionalLocalStateStep(node1, state1, node2, state2, config) or - additionalJumpStateStep(node1, state1, node2, state2, config) - | - revFlow(node1, _, pragma[only_bind_into](config)) and - revFlow(node2, _, pragma[only_bind_into](config)) and - fwdFlowState(state1, pragma[only_bind_into](config)) and - fwdFlowState(state2, pragma[only_bind_into](config)) - ) - } - - additional predicate revFlowState(FlowState state, Configuration config) { - exists(NodeEx node | - sinkNode(node, state, config) and - revFlow(node, _, pragma[only_bind_into](config)) and - fwdFlowState(state, pragma[only_bind_into](config)) - ) - or - exists(FlowState state0 | - revFlowState(state0, config) and - stateStepRev(state, state0, config) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Content c | - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - revFlow(node2, pragma[only_bind_into](config)) and - store(node1, tc, node2, contentType, config) and - c = tc.getContent() and - exists(ap1) - ) - } - - pragma[nomagic] - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) { - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - read(n1, c, n2, pragma[only_bind_into](config)) and - revFlow(n2, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, config) and - exists(ap) - } - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, _, pragma[only_bind_into](config)) and - exists(state) and - exists(ap) - } - - private predicate throughFlowNodeCand(NodeEx node, Configuration config) { - revFlow(node, true, config) and - fwdFlow(node, true, config) and - not inBarrier(node, config) and - not outBarrier(node, config) - } - - /** Holds if flow may return from `callable`. */ - pragma[nomagic] - private predicate returnFlowCallableNodeCand( - DataFlowCallable callable, ReturnKindExt kind, Configuration config - ) { - exists(RetNodeEx ret | - throughFlowNodeCand(ret, config) and - callable = ret.getEnclosingCallable() and - kind = ret.getKind() - ) - } - - /** - * Holds if flow may enter through `p` and reach a return node making `p` a - * candidate for the origin of a summary. - */ - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(DataFlowCallable c, ReturnKindExt kind | - throughFlowNodeCand(p, config) and - returnFlowCallableNodeCand(c, kind, config) and - p.getEnclosingCallable() = c and - exists(ap) and - parameterFlowThroughAllowed(p, kind) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - throughFlowNodeCand(ret, config) and - kind = ret.getKind() and - exists(argAp) and - exists(ap) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, boolean toReturn | - revFlow(arg, toReturn, config) and - revFlowInToReturn(call, arg, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, config)) and - fields = count(Content f0 | fwdFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | fwdFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, config)) and - fields = count(Content f0 | revFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | revFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | revFlow(n, b, config)) - } - /* End: Stage 1 logic. */ -} - -pragma[noinline] -private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - localFlowStep(node1, node2, config) -} - -pragma[noinline] -private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - additionalLocalFlowStep(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCand1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config -) { - Stage1::revFlow(out, config) and - Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, Configuration config -) { - exists(ReturnPosition pos | - viableReturnPosOutNodeCand1(call, pos, out, config) and - pos = ret.getReturnPosition() and - kind = pos.getKind() and - Stage1::revFlow(ret, config) and - not outBarrier(ret, config) and - not inBarrier(out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCand1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config -) { - Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and - Stage1::revFlow(arg, config) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config -) { - viableParamArgNodeCand1(call, p, arg, config) and - Stage1::revFlow(p, config) and - not outBarrier(arg, config) and - not inBarrier(p, config) -} - -/** - * Gets the amount of forward branching on the origin of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int branch(NodeEx n1, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n1, _, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) - ) -} - -/** - * Gets the amount of backward branching on the target of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int join(NodeEx n2, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n, _, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) - ) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. The - * `allowsFieldFlow` flag indicates whether the branching is within the limit - * specified by the configuration. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, ret, kind, out, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(ret, pragma[only_bind_into](config)) and - j = join(out, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. The `allowsFieldFlow` flag indicates whether - * the branching is within the limit specified by the configuration. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config -) { - flowIntoCallNodeCand1(call, arg, p, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(arg, pragma[only_bind_into](config)) and - j = join(p, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -private signature module StageSig { - class Ap; - - predicate revFlow(NodeEx node, Configuration config); - - predicate revFlowAp(NodeEx node, Ap ap, Configuration config); - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config); - - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config); - - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config); - - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ); - - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ); - - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config); -} - -private module MkStage { - class ApApprox = PrevStage::Ap; - - signature module StageParam { - class Ap; - - class ApNil extends Ap; - - bindingset[result, ap] - ApApprox getApprox(Ap ap); - - ApNil getApNil(NodeEx node); - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail); - - /** - * An approximation of `Content` that corresponds to the precision level of - * `Ap`, such that the mappings from both `Ap` and `Content` to this type - * are functional. - */ - class ApHeadContent; - - ApHeadContent getHeadContent(Ap ap); - - ApHeadContent projectToHeadContent(Content c); - - class ApOption; - - ApOption apNone(); - - ApOption apSome(Ap ap); - - class Cc; - - class CcCall extends Cc; - - // TODO: member predicate on CcCall - predicate matchesCall(CcCall cc, DataFlowCall call); - - class CcNoCall extends Cc; - - Cc ccNone(); - - CcCall ccSomeCall(); - - class LocalCc; - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc); - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ); - - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config - ); - - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config - ); - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config); - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType); - } - - module Stage implements StageSig { - import Param - - /* Begin: Stage logic. */ - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - private predicate revFlowApAlias(NodeEx node, ApApprox apa, Configuration config) { - PrevStage::revFlowAp(node, apa, config) - } - - pragma[nomagic] - private predicate flowIntoCallApa( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa, - Configuration config - ) { - flowIntoCall(call, arg, p, allowsFieldFlow, config) and - PrevStage::revFlowAp(p, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(arg, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowOutOfCallApa( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - ApApprox apa, Configuration config - ) { - flowOutOfCall(call, ret, kind, out, allowsFieldFlow, config) and - PrevStage::revFlowAp(out, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(ret, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowThroughOutOfCall( - DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, - ApApprox argApa, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa, pragma[only_bind_into](config)) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) and - matchesCall(ccc, call) - ) - } - - /** - * Holds if `node` is reachable with access path `ap` from a source in the - * configuration `config`. - * - * The call context `cc` records whether the node is reached through an - * argument in a call, and if so, `summaryCtx` and `argAp` record the - * corresponding parameter position and access path of that argument, respectively. - */ - pragma[nomagic] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa, config) and - PrevStage::revFlow(node, state, apa, config) and - filter(node, state, ap, config) - } - - pragma[inline] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, cc, summaryCtx, argAp, ap, _, config) - } - - pragma[nomagic] - private predicate fwdFlow0( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - sourceNode(node, state, config) and - (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and - argAp = apNone() and - summaryCtx = TParamNodeNone() and - ap = getApNil(node) and - apa = getApprox(ap) - or - exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | - fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0, config) and - localCc = getLocalCc(mid, cc) - | - localStep(mid, state0, node, state, true, _, config, localCc) and - ap = ap0 and - apa = apa0 - or - localStep(mid, state0, node, state, false, ap, config, localCc) and - ap0 instanceof ApNil and - apa = getApprox(ap) - ) - or - exists(NodeEx mid | - fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa, pragma[only_bind_into](config)) and - jumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(mid, state, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(mid, state0, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStateStep(mid, state0, node, state, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - // store - exists(TypedContent tc, Ap ap0 | - fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp, config) and - ap = apCons(tc, ap0) and - apa = getApprox(ap) - ) - or - // read - exists(Ap ap0, Content c | - fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp, config) and - fwdFlowConsCand(ap0, c, ap, config) and - apa = getApprox(ap) - ) - or - // flow into a callable - fwdFlowIn(_, node, state, _, cc, _, _, ap, apa, config) and - if PrevStage::parameterMayFlowThrough(node, apa, config) - then ( - summaryCtx = TParamNodeSome(node.asNode()) and - argAp = apSome(ap) - ) else ( - summaryCtx = TParamNodeNone() and argAp = apNone() - ) - or - // flow out of a callable - exists( - DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, - DataFlowCallable inner - | - fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa, config) and - flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa, config) and - inner = ret.getEnclosingCallable() and - cc = getCallContextReturn(inner, call, innercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - or - // flow through a callable - exists( - DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, ApApprox innerArgApa - | - fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowStore( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(DataFlowType contentType, ApApprox apa1 | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1, config) and - PrevStage::storeStepCand(node1, apa1, tc, node2, contentType, config) and - typecheckStore(ap1, contentType) - ) - } - - /** - * Holds if forward flow with access path `tail` reaches a store of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(TypedContent tc | - fwdFlowStore(_, tail, tc, _, _, _, _, _, config) and - tc.getContent() = c and - cons = apCons(tc, tail) - ) - } - - pragma[nomagic] - private predicate readStepCand( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - PrevStage::readStepCand(node1, c, node2, config) and - apc = projectToHeadContent(c) - } - - bindingset[node1, apc] - pragma[inline_late] - private predicate readStepCand0( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - readStepCand(node1, apc, c, node2, config) - } - - pragma[nomagic] - private predicate fwdFlowRead( - Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(ApHeadContent apc | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap, config) and - apc = getHeadContent(ap) and - readStepCand0(node1, apc, c, node2, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowIn( - DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, - ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa, Configuration config - ) { - exists(ArgNodeEx arg, boolean allowsFieldFlow | - fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa, config) and - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowRetFromArg( - RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, ApApprox argApa, - Ap ap, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - fwdFlow(pragma[only_bind_into](ret), state, ccc, - TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), - pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa), - pragma[only_bind_into](config)) and - kind = ret.getKind() and - parameterFlowThroughAllowed(summaryCtx, kind) and - argApa = getApprox(argAp) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) - ) - } - - pragma[inline] - private predicate fwdFlowThrough0( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, - Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa, config) and - fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp, config) - } - - pragma[nomagic] - private predicate fwdFlowThrough( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa, - config) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` - * and data might flow through the target callable and back out at `call`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered( - DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, - ParamNodeEx p, Ap ap, Configuration config - ) { - exists(ApApprox apa | - fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(p, apa, config) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate storeStepFwd( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config - ) { - fwdFlowStore(node1, ap1, tc, node2, _, _, _, _, config) and - ap2 = apCons(tc, ap1) and - fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _, config) - } - - private predicate readStepFwd( - NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config - ) { - fwdFlowRead(ap1, c, n1, n2, _, _, _, _, config) and - fwdFlowConsCand(ap1, c, ap2, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough0( - DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, - ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, - innerArgApa, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough( - RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, - Ap ap, Configuration config - ) { - exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | - returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa, config) and - pos = ret.getReturnPosition() and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowThroughIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap, - Configuration config - ) { - exists(ApApprox argApa | - flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), - allowsFieldFlow, argApa, pragma[only_bind_into](config)) and - fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa, - pragma[only_bind_into](config)) and - returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap, - pragma[only_bind_into](config)) and - if allowsFieldFlow = false then argAp instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowIntoCallAp( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap, - Configuration config - ) { - exists(ApApprox apa | - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - fwdFlow(arg, _, _, _, _, ap, apa, config) - ) - } - - pragma[nomagic] - private predicate flowOutOfCallAp( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, - Ap ap, Configuration config - ) { - exists(ApApprox apa | - flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa, config) and - fwdFlow(ret, _, _, _, _, ap, apa, config) and - pos = ret.getReturnPosition() - ) - } - - /** - * Holds if `node` with access path `ap` is part of a path from a source to a - * sink in the configuration `config`. - * - * The parameter `returnCtx` records whether (and how) the node must be returned - * from the enclosing callable in order to reach a sink, and if so, `returnAp` - * records the access path of the returned value. - */ - pragma[nomagic] - additional predicate revFlow( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - revFlow0(node, state, returnCtx, returnAp, ap, config) and - fwdFlow(node, state, _, _, _, ap, config) - } - - pragma[nomagic] - private predicate revFlow0( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, _, _, _, ap, config) and - sinkNode(node, state, config) and - ( - if hasSinkCallCtx(config) - then returnCtx = TReturnCtxNoFlowThrough() - else returnCtx = TReturnCtxNone() - ) and - returnAp = apNone() and - ap instanceof ApNil - or - exists(NodeEx mid, FlowState state0 | - localStep(node, state, mid, state0, true, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, ap, config) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap, pragma[only_bind_into](config)) and - localStep(node, pragma[only_bind_into](state), mid, state0, false, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, nil, pragma[only_bind_into](config)) and - ap instanceof ApNil - ) - or - exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, state, _, _, ap, config) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStep(node, mid, config) and - revFlow(pragma[only_bind_into](mid), state, _, _, nil, pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStateStep(node, state, mid, state0, config) and - revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil, - pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - // store - exists(Ap ap0, Content c | - revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp, config) and - revFlowConsCand(ap0, c, ap, config) - ) - or - // read - exists(NodeEx mid, Ap ap0 | - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - readStepFwd(node, ap, _, mid, ap0, config) - ) - or - // flow into a callable - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, state, TReturnCtxNone(), returnAp, ap, config) and - flowIntoCallAp(_, node, p, allowsFieldFlow, ap, config) and - (if allowsFieldFlow = false then ap instanceof ApNil else any()) and - returnCtx = TReturnCtxNone() - ) - or - // flow through a callable - exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, node, p, _, ap, innerReturnAp, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(_, node, pos, state, _, _, ap, config) and - if returnFlowsThrough(node, pos, state, _, _, _, ap, config) - then ( - returnCtx = TReturnCtxMaybeFlowThrough(pos) and - returnAp = apSome(ap) - ) else ( - returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() - ) - ) - } - - pragma[nomagic] - private predicate revFlowStore( - Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, - ReturnCtx returnCtx, ApOption returnAp, Configuration config - ) { - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - storeStepFwd(node, ap, tc, mid, ap0, config) and - tc.getContent() = c - } - - /** - * Holds if reverse flow with access path `tail` reaches a read of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(NodeEx mid, Ap tail0 | - revFlow(mid, _, _, _, tail, config) and - tail = pragma[only_bind_into](tail0) and - readStepFwd(_, cons, c, mid, tail0, config) - ) - } - - pragma[nomagic] - private predicate revFlowOut( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, - ApOption returnAp, Ap ap, Configuration config - ) { - exists(NodeEx out, boolean allowsFieldFlow | - revFlow(out, state, returnCtx, returnAp, ap, config) and - flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate revFlowParamToReturn( - ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap, Configuration config - ) { - revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), - pragma[only_bind_into](ap), pragma[only_bind_into](config)) and - parameterFlowThroughAllowed(p, pos.getKind()) and - PrevStage::parameterMayFlowThrough(p, getApprox(ap), config) - } - - pragma[nomagic] - private predicate revFlowThrough( - DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, - ApOption returnAp, Ap ap, Ap innerReturnAp, Configuration config - ) { - revFlowParamToReturn(p, state, pos, innerReturnAp, ap, config) and - revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned( - DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap, - Configuration config - ) { - exists(RetNodeEx ret, FlowState state, CcCall ccc | - revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap, config) and - returnFlowsThrough(ret, pos, state, ccc, _, _, ap, config) and - matchesCall(ccc, call) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Ap ap2, Content c | - PrevStage::storeStepCand(node1, _, tc, node2, contentType, config) and - revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _, config) and - revFlowConsCand(ap2, c, ap1, config) - ) - } - - predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(Ap ap1, Ap ap2 | - revFlow(node2, _, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and - readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _, - pragma[only_bind_into](config)) - ) - } - - additional predicate revFlow(NodeEx node, FlowState state, Configuration config) { - revFlow(node, state, _, _, _, config) - } - - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, _, _, ap, config) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, _, _, _, ap, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, Configuration config) { - revFlow(node, _, _, _, _, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, ap, config) - } - - private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepFwd(_, ap, tc, _, _, config) - } - - private predicate revConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepCand(_, ap, tc, _, _, config) - } - - private predicate validAp(Ap ap, Configuration config) { - revFlow(_, _, _, _, ap, config) and ap instanceof ApNil - or - exists(TypedContent head, Ap tail | - consCand(head, tail, config) and - ap = apCons(head, tail) - ) - } - - additional predicate consCand(TypedContent tc, Ap ap, Configuration config) { - revConsCand(tc, ap, config) and - validAp(ap, config) - } - - pragma[nomagic] - private predicate parameterFlowsThroughRev( - ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp, Configuration config - ) { - revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap, config) and - parameterFlowThroughAllowed(p, pos.getKind()) - } - - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(ReturnPosition pos | - returnFlowsThrough(_, pos, _, _, p, ap, _, config) and - parameterFlowsThroughRev(p, ap, pos, _, config) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - exists(ParamNodeEx p, ReturnPosition pos | - returnFlowsThrough(ret, pos, _, _, p, argAp, ap, config) and - parameterFlowsThroughRev(p, argAp, pos, ap, config) and - kind = pos.getKind() - ) - } - - pragma[nomagic] - private predicate revFlowThroughArg( - DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, - Ap ap, Configuration config - ) { - exists(ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp, config) - ) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | - revFlow(arg, state, returnCtx, returnAp, ap, config) and - revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, config)) and - fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and - states = count(FlowState state | fwdFlow(_, state, _, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | - fwdFlow(n, state, cc, summaryCtx, argAp, ap, config) - ) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, _, _, _, config)) and - fields = count(TypedContent f0 | consCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and - states = count(FlowState state | revFlow(_, state, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | - revFlow(n, state, returnCtx, retAp, ap, config) - ) - } - /* End: Stage logic. */ - } -} - -private module BooleanCallContext { - class Cc extends boolean { - Cc() { this in [true, false] } - } - - class CcCall extends Cc { - CcCall() { this = true } - } - - /** Holds if the call context may be `call`. */ - predicate matchesCall(CcCall cc, DataFlowCall call) { any() } - - class CcNoCall extends Cc { - CcNoCall() { this = false } - } - - Cc ccNone() { result = false } - - CcCall ccSomeCall() { result = true } - - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } -} - -private module Level1CallContext { - class Cc = CallContext; - - class CcCall = CallContextCall; - - pragma[inline] - predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } - - class CcNoCall = CallContextNoCall; - - Cc ccNone() { result instanceof CallContextAny } - - CcCall ccSomeCall() { result instanceof CallContextSomeCall } - - module NoLocalCallContext { - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSiteDispatch(call, c) - then result = TSpecificCall(call) - else result = TSomeCall() - } - } - - module LocalCallContext { - class LocalCc = LocalCallContext; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { - result = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - node.getEnclosingCallable()) - } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() - } - } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { - checkCallContextReturn(innercc, c, call) and - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() - } -} - -private module Stage2Param implements MkStage::StageParam { - private module PrevStage = Stage1; - - class Ap extends boolean { - Ap() { this in [true, false] } - } - - class ApNil extends Ap { - ApNil() { this = false } - } - - bindingset[result, ap] - PrevStage::Ap getApprox(Ap ap) { any() } - - ApNil getApNil(NodeEx node) { Stage1::revFlow(node, _) and exists(result) } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } - - class ApHeadContent = Unit; - - pragma[inline] - ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } - - ApHeadContent projectToHeadContent(Content c) { any() } - - class ApOption = BooleanOption; - - ApOption apNone() { result = TBooleanNone() } - - ApOption apSome(Ap ap) { result = TBooleanSome(ap) } - - import Level1CallContext - import NoLocalCallContext - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - ( - preservesValue = true and - localFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalStateStep(node1, state1, node2, state2, config) - ) and - exists(ap) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand1/6; - - predicate flowIntoCall = flowIntoCallNodeCand1/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - expectsContentEx(node, c) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - PrevStage::revFlowState(state, pragma[only_bind_into](config)) and - exists(ap) and - not stateBarrier(node, state, config) and - ( - notExpectsContent(node) - or - ap = true and - expectsContentCand(node, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage2 implements StageSig { - import MkStage::Stage -} - -pragma[nomagic] -private predicate flowOutOfCallNodeCand2( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -pragma[nomagic] -private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -private module LocalFlowBigStep { - /** - * A node where some checking is required, and hence the big-step relation - * is not allowed to step over. - */ - private class FlowCheckNode extends NodeEx { - FlowCheckNode() { - castNode(this.asNode()) or - clearsContentCached(this.asNode(), _) or - expectsContentCached(this.asNode(), _) - } - } - - /** - * Holds if `node` can be the first node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowEntry(NodeEx node, FlowState state, Configuration config) { - Stage2::revFlow(node, state, config) and - ( - sourceNode(node, state, config) - or - jumpStep(_, node, config) - or - additionalJumpStep(_, node, config) - or - additionalJumpStateStep(_, _, node, state, config) - or - node instanceof ParamNodeEx - or - node.asNode() instanceof OutNodeExt - or - Stage2::storeStepCand(_, _, _, node, _, config) - or - Stage2::readStepCand(_, _, node, config) - or - node instanceof FlowCheckNode - or - exists(FlowState s | - additionalLocalStateStep(_, s, node, state, config) and - s != state - ) - ) - } - - /** - * Holds if `node` can be the last node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowExit(NodeEx node, FlowState state, Configuration config) { - exists(NodeEx next | Stage2::revFlow(next, state, config) | - jumpStep(node, next, config) or - additionalJumpStep(node, next, config) or - flowIntoCallNodeCand2(_, node, next, _, config) or - flowOutOfCallNodeCand2(_, node, _, next, _, config) or - Stage2::storeStepCand(node, _, _, next, _, config) or - Stage2::readStepCand(node, _, next, config) - ) - or - exists(NodeEx next, FlowState s | Stage2::revFlow(next, s, config) | - additionalJumpStateStep(node, state, next, s, config) - or - additionalLocalStateStep(node, state, next, s, config) and - s != state - ) - or - Stage2::revFlow(node, state, config) and - node instanceof FlowCheckNode - or - sinkNode(node, state, config) - } - - pragma[noinline] - private predicate additionalLocalFlowStepNodeCand2( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, Configuration config - ) { - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 and - Stage2::revFlow(node1, pragma[only_bind_into](state1), false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false, - pragma[only_bind_into](config)) - or - additionalLocalStateStep(node1, state1, node2, state2, config) and - Stage2::revFlow(node1, state1, false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, state2, false, pragma[only_bind_into](config)) - } - - /** - * Holds if the local path from `node1` to `node2` is a prefix of a maximal - * subsequence of local flow steps in a dataflow path. - * - * This is the transitive closure of `[additional]localFlowStep` beginning - * at `localFlowEntry`. - */ - pragma[nomagic] - private predicate localFlowStepPlus( - NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, - Configuration config, LocalCallContext cc - ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and - ( - localFlowEntry(node1, pragma[only_bind_into](state), pragma[only_bind_into](config)) and - ( - localFlowStepNodeCand1(node1, node2, config) and - preservesValue = true and - t = node1.getDataFlowType() and // irrelevant dummy value - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - or - additionalLocalFlowStepNodeCand2(node1, state, node2, state, config) and - preservesValue = false and - t = node2.getDataFlowType() - ) and - node1 != node2 and - cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) - or - exists(NodeEx mid | - localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, - pragma[only_bind_into](config), cc) and - localFlowStepNodeCand1(mid, node2, config) and - not mid instanceof FlowCheckNode and - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - ) - or - exists(NodeEx mid | - localFlowStepPlus(node1, state, mid, _, _, pragma[only_bind_into](config), cc) and - additionalLocalFlowStepNodeCand2(mid, state, node2, state, config) and - not mid instanceof FlowCheckNode and - preservesValue = false and - t = node2.getDataFlowType() - ) - ) - } - - /** - * Holds if `node1` can step to `node2` in one or more local steps and this - * path can occur as a maximal subsequence of local steps in a dataflow path. - */ - pragma[nomagic] - predicate localFlowBigStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - DataFlowType t, Configuration config, LocalCallContext callContext - ) { - localFlowStepPlus(node1, state1, node2, preservesValue, t, config, callContext) and - localFlowExit(node2, state1, config) and - state1 = state2 - or - additionalLocalFlowStepNodeCand2(node1, state1, node2, state2, config) and - state1 != state2 and - preservesValue = false and - t = node2.getDataFlowType() and - callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) - } -} - -private import LocalFlowBigStep - -private module Stage3Param implements MkStage::StageParam { - private module PrevStage = Stage2; - - class Ap = ApproxAccessPathFront; - - class ApNil = ApproxAccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } - - class ApHeadContent = ContentApprox; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - predicate projectToHeadContent = getContentApprox/1; - - class ApOption = ApproxAccessPathFrontOption; - - ApOption apNone() { result = TApproxAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } - - import BooleanCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApproxAccessPathFrontNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand2/6; - - predicate flowIntoCall = flowIntoCallNodeCand2/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getAHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage3 implements StageSig { - import MkStage::Stage -} - -private module Stage4Param implements MkStage::StageParam { - private module PrevStage = Stage3; - - class Ap = AccessPathFront; - - class ApNil = AccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathFrontOption; - - ApOption apNone() { result = TAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } - - import BooleanCallContext - - pragma[nomagic] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) and - exists(lcc) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clearSet(NodeEx node, ContentSet c, Configuration config) { - PrevStage::revFlow(node, config) and - clearsContentCached(node.asNode(), c) - } - - pragma[nomagic] - private predicate clearContent(NodeEx node, Content c, Configuration config) { - exists(ContentSet cs | - PrevStage::readStepCand(_, pragma[only_bind_into](c), _, pragma[only_bind_into](config)) and - c = cs.getAReadContent() and - clearSet(node, cs, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clear(NodeEx node, Ap ap, Configuration config) { - clearContent(node, ap.getHead().getContent(), config) - } - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - not clear(node, ap, config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage4 implements StageSig { - import MkStage::Stage -} - -/** - * Holds if `argApf` is recorded as the summary context for flow reaching `node` - * and remains relevant for the following pruning stage. - */ -private predicate flowCandSummaryCtx( - NodeEx node, FlowState state, AccessPathFront argApf, Configuration config -) { - exists(AccessPathFront apf | - Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf, config) and - Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf, - config) - ) -} - -/** - * Holds if a length 2 access path approximation with the head `tc` is expected - * to be expensive. - */ -private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { - exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf, config)) and - nodes = - strictcount(NodeEx n, FlowState state | - Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - or - flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - ) and - accessPathApproxCostLimits(apLimit, tupleLimit) and - apLimit < tails and - tupleLimit < (tails - 1) * nodes and - not tc.forceHighPrecision() - ) -} - -private newtype TAccessPathApprox = - TNil(DataFlowType t) or - TConsNil(TypedContent tc, DataFlowType t) { - Stage4::consCand(tc, TFrontNil(t), _) and - not expensiveLen2unfolding(tc, _) - } or - TConsCons(TypedContent tc1, TypedContent tc2, int len) { - Stage4::consCand(tc1, TFrontHead(tc2), _) and - len in [2 .. accessPathLimit()] and - not expensiveLen2unfolding(tc1, _) - } or - TCons1(TypedContent tc, int len) { - len in [1 .. accessPathLimit()] and - expensiveLen2unfolding(tc, _) - } - -/** - * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only - * the first two elements of the list and its length are tracked. If data flows - * from a source to a given node with a given `AccessPathApprox`, this indicates - * the sequence of dereference operations needed to get from the value in the node - * to the tracked object. The final type indicates the type of the tracked object. - */ -abstract private class AccessPathApprox extends TAccessPathApprox { - abstract string toString(); - - abstract TypedContent getHead(); - - abstract int len(); - - abstract DataFlowType getType(); - - abstract AccessPathFront getFront(); - - /** Gets the access path obtained by popping `head` from this path, if any. */ - abstract AccessPathApprox pop(TypedContent head); -} - -private class AccessPathApproxNil extends AccessPathApprox, TNil { - private DataFlowType t; - - AccessPathApproxNil() { this = TNil(t) } - - override string toString() { result = concat(": " + ppReprType(t)) } - - override TypedContent getHead() { none() } - - override int len() { result = 0 } - - override DataFlowType getType() { result = t } - - override AccessPathFront getFront() { result = TFrontNil(t) } - - override AccessPathApprox pop(TypedContent head) { none() } -} - -abstract private class AccessPathApproxCons extends AccessPathApprox { } - -private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { - private TypedContent tc; - private DataFlowType t; - - AccessPathApproxConsNil() { this = TConsNil(tc, t) } - - override string toString() { - // The `concat` becomes "" if `ppReprType` has no result. - result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) - } - - override TypedContent getHead() { result = tc } - - override int len() { result = 1 } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } -} - -private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { - private TypedContent tc1; - private TypedContent tc2; - private int len; - - AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - - override string toString() { - if len = 2 - then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" - else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc1 } - - override int len() { result = len } - - override DataFlowType getType() { result = tc1.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc1) } - - override AccessPathApprox pop(TypedContent head) { - head = tc1 and - ( - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - } -} - -private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { - private TypedContent tc; - private int len; - - AccessPathApproxCons1() { this = TCons1(tc, len) } - - override string toString() { - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc } - - override int len() { result = len } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { - head = tc and - ( - exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2), _) | - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - or - exists(DataFlowType t | - len = 1 and - Stage4::consCand(tc, TFrontNil(t), _) and - result = TNil(t) - ) - ) - } -} - -/** Gets the access path obtained by popping `tc` from `ap`, if any. */ -private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } - -/** Gets the access path obtained by pushing `tc` onto `ap`. */ -private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } - -private newtype TAccessPathApproxOption = - TAccessPathApproxNone() or - TAccessPathApproxSome(AccessPathApprox apa) - -private class AccessPathApproxOption extends TAccessPathApproxOption { - string toString() { - this = TAccessPathApproxNone() and result = "" - or - this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) - } -} - -private module Stage5Param implements MkStage::StageParam { - private module PrevStage = Stage4; - - class Ap = AccessPathApprox; - - class ApNil = AccessPathApproxNil; - - pragma[nomagic] - PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathApproxOption; - - ApOption apNone() { result = TAccessPathApproxNone() } - - ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } - - import Level1CallContext - import LocalCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, lcc) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { any() } - - // Type checking is not necessary here as it has already been done in stage 3. - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage5 = MkStage::Stage; - -bindingset[conf, result] -private Configuration unbindConf(Configuration conf) { - exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c)) -} - -pragma[nomagic] -private predicate nodeMayUseSummary0( - NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(AccessPathApprox apa0 | - Stage5::parameterMayFlowThrough(p, _, _) and - Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0, config) and - Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), - TAccessPathApproxSome(apa), apa0, config) - ) -} - -pragma[nomagic] -private predicate nodeMayUseSummary( - NodeEx n, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::parameterMayFlowThrough(p, apa, config) and - nodeMayUseSummary0(n, p, state, apa, config) - ) -} - -private newtype TSummaryCtx = - TSummaryCtxNone() or - TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { - exists(Configuration config | - Stage5::parameterMayFlowThrough(p, ap.getApprox(), config) and - Stage5::revFlow(p, state, _, config) - ) - } - -/** - * A context for generating flow summaries. This represents flow entry through - * a specific parameter with an access path of a specific shape. - * - * Summaries are only created for parameters that may flow through. - */ -abstract private class SummaryCtx extends TSummaryCtx { - abstract string toString(); -} - -/** A summary context from which no flow summary can be generated. */ -private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { - override string toString() { result = "" } -} - -/** A summary context from which a flow summary can be generated. */ -private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParamNodeEx p; - private FlowState s; - private AccessPath ap; - - SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } - - ParameterPosition getParameterPos() { p.isParameterOf(_, result) } - - ParamNodeEx getParamNode() { result = p } - - override string toString() { result = p + ": " + ap } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** - * Gets the number of length 2 access path approximations that correspond to `apa`. - */ -private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { - exists(TypedContent tc, int len | - tc = apa.getHead() and - len = apa.len() and - result = - strictcount(AccessPathFront apf | - Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), - config) - ) - ) -} - -private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = - strictcount(NodeEx n, FlowState state | - Stage5::revFlow(n, state, apa, config) or nodeMayUseSummary(n, state, apa, config) - ) -} - -/** - * Holds if a length 2 access path approximation matching `apa` is expected - * to be expensive. - */ -private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = count1to2unfold(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - apLimit < aps and - tupleLimit < (aps - 1) * nodes - ) -} - -private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { - exists(TypedContent head | - apa.pop(head) = result and - Stage5::consCand(head, result, config) - ) -} - -/** - * Holds with `unfold = false` if a precise head-tail representation of `apa` is - * expected to be expensive. Holds with `unfold = true` otherwise. - */ -private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { - if apa.getHead().forceHighPrecision() - then unfold = true - else - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = countPotentialAps(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true - ) -} - -/** - * Gets the number of `AccessPath`s that correspond to `apa`. - */ -pragma[assume_small_delta] -private int countAps(AccessPathApprox apa, Configuration config) { - evalUnfold(apa, false, config) and - result = 1 and - (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) - or - evalUnfold(apa, false, config) and - result = count1to2unfold(apa, config) and - not expensiveLen1to2unfolding(apa, config) - or - evalUnfold(apa, true, config) and - result = countPotentialAps(apa, config) -} - -/** - * Gets the number of `AccessPath`s that would correspond to `apa` assuming - * that it is expanded to a precise head-tail representation. - */ -language[monotonicAggregates] -pragma[assume_small_delta] -private int countPotentialAps(AccessPathApprox apa, Configuration config) { - apa instanceof AccessPathApproxNil and result = 1 - or - result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) -} - -private newtype TAccessPath = - TAccessPathNil(DataFlowType t) or - TAccessPathCons(TypedContent head, AccessPath tail) { - exists(AccessPathApproxCons apa | - not evalUnfold(apa, false, _) and - head = apa.getHead() and - tail.getApprox() = getATail(apa, _) - ) - } or - TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - not expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head1 = apa.getHead() and - head2 = getATail(apa, _).getHead() - ) - } or - TAccessPathCons1(TypedContent head, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head = apa.getHead() - ) - } - -private newtype TPathNode = - pragma[assume_small_delta] - TPathNodeMid( - NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config - ) { - // A PathNode is introduced by a source ... - Stage5::revFlow(node, state, config) and - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - or - // ... or a step from an existing PathNode to another node. - exists(PathNodeMid mid | - pathStep(mid, node, state, cc, sc, ap) and - pragma[only_bind_into](config) = mid.getConfiguration() and - Stage5::revFlow(node, state, ap.getApprox(), pragma[only_bind_into](config)) - ) - } or - TPathNodeSink(NodeEx node, FlowState state, Configuration config) { - exists(PathNodeMid sink | - sink.isAtSink() and - node = sink.getNodeEx() and - state = sink.getState() and - config = sink.getConfiguration() - ) - } or - TPathNodeSourceGroup(string sourceGroup, Configuration config) { - exists(PathNodeImpl source | - sourceGroup = source.getSourceGroup() and - config = source.getConfiguration() - ) - } or - TPathNodeSinkGroup(string sinkGroup, Configuration config) { - exists(PathNodeSink sink | - sinkGroup = sink.getSinkGroup() and - config = sink.getConfiguration() - ) - } - -/** - * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a - * source to a given node with a given `AccessPath`, this indicates the sequence - * of dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ -private class AccessPath extends TAccessPath { - /** Gets the head of this access path, if any. */ - abstract TypedContent getHead(); - - /** Gets the tail of this access path, if any. */ - abstract AccessPath getTail(); - - /** Gets the front of this access path. */ - abstract AccessPathFront getFront(); - - /** Gets the approximation of this access path. */ - abstract AccessPathApprox getApprox(); - - /** Gets the length of this access path. */ - abstract int length(); - - /** Gets a textual representation of this access path. */ - abstract string toString(); - - /** Gets the access path obtained by popping `tc` from this access path, if any. */ - final AccessPath pop(TypedContent tc) { - result = this.getTail() and - tc = this.getHead() - } - - /** Gets the access path obtained by pushing `tc` onto this access path. */ - final AccessPath push(TypedContent tc) { this = result.pop(tc) } -} - -private class AccessPathNil extends AccessPath, TAccessPathNil { - private DataFlowType t; - - AccessPathNil() { this = TAccessPathNil(t) } - - DataFlowType getType() { result = t } - - override TypedContent getHead() { none() } - - override AccessPath getTail() { none() } - - override AccessPathFrontNil getFront() { result = TFrontNil(t) } - - override AccessPathApproxNil getApprox() { result = TNil(t) } - - override int length() { result = 0 } - - override string toString() { result = concat(": " + ppReprType(t)) } -} - -private class AccessPathCons extends AccessPath, TAccessPathCons { - private TypedContent head; - private AccessPath tail; - - AccessPathCons() { this = TAccessPathCons(head, tail) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { result = tail } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - pragma[assume_small_delta] - override AccessPathApproxCons getApprox() { - result = TConsNil(head, tail.(AccessPathNil).getType()) - or - result = TConsCons(head, tail.getHead(), this.length()) - or - result = TCons1(head, this.length()) - } - - pragma[assume_small_delta] - override int length() { result = 1 + tail.length() } - - private string toStringImpl(boolean needsSuffix) { - exists(DataFlowType t | - tail = TAccessPathNil(t) and - needsSuffix = false and - result = head.toString() + "]" + concat(" : " + ppReprType(t)) - ) - or - result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) - or - exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | - result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true - or - result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false - ) - or - exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | - result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true - or - result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false - ) - } - - override string toString() { - result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" - or - result = "[" + this.toStringImpl(false) - } -} - -private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { - private TypedContent head1; - private TypedContent head2; - private int len; - - AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } - - override TypedContent getHead() { result = head1 } - - override AccessPath getTail() { - Stage5::consCand(head1, result.getApprox(), _) and - result.getHead() = head2 and - result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head1) } - - override AccessPathApproxCons getApprox() { - result = TConsCons(head1, head2, len) or - result = TCons1(head1, len) - } - - override int length() { result = len } - - override string toString() { - if len = 2 - then result = "[" + head1.toString() + ", " + head2.toString() + "]" - else - result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" - } -} - -private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { - private TypedContent head; - private int len; - - AccessPathCons1() { this = TAccessPathCons1(head, len) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { - Stage5::consCand(head, result.getApprox(), _) and result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - - override int length() { result = len } - - override string toString() { - if len = 1 - then result = "[" + head.toString() + "]" - else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" - } -} - -abstract private class PathNodeImpl extends TPathNode { - /** Gets the `FlowState` of this node. */ - abstract FlowState getState(); - - /** Gets the associated configuration. */ - abstract Configuration getConfiguration(); - - /** Holds if this node is a source. */ - abstract predicate isSource(); - - abstract PathNodeImpl getASuccessorImpl(); - - private PathNodeImpl getASuccessorIfHidden() { - this.isHidden() and - result = this.getASuccessorImpl() - } - - pragma[nomagic] - private PathNodeImpl getANonHiddenSuccessor0() { - result = this.getASuccessorIfHidden*() and - not result.isHidden() - } - - final PathNodeImpl getANonHiddenSuccessor() { - result = this.getASuccessorImpl().getANonHiddenSuccessor0() and - not this.isHidden() - } - - abstract NodeEx getNodeEx(); - - predicate isHidden() { - not this.getConfiguration().includeHiddenNodes() and - ( - hiddenNode(this.getNodeEx().asNode()) and - not this.isSource() and - not this instanceof PathNodeSink - or - this.getNodeEx() instanceof TNodeImplicitRead - ) - } - - string getSourceGroup() { - this.isSource() and - this.getConfiguration().sourceGrouping(this.getNodeEx().asNode(), result) + not singleConfiguration() and + getConfig(state1).isAdditionalFlowStep(node1, node2) and + state2 = state1 } - predicate isFlowSource() { - this.isSource() and not exists(this.getSourceGroup()) - or - this instanceof PathNodeSourceGroup + predicate allowImplicitRead(Node node, ContentSet c) { + any(Configuration config).allowImplicitRead(node, c) } - predicate isFlowSink() { - this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or - this instanceof PathNodeSinkGroup - } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } - private string ppAp() { - this instanceof PathNodeSink and result = "" - or - exists(string s | s = this.(PathNodeMid).getAp().toString() | - if s = "" then result = "" else result = " " + s - ) - } + FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } - private string ppCtx() { - this instanceof PathNodeSink and result = "" - or - result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + predicate sourceGrouping(Node source, string sourceGroup) { + any(Configuration config).sourceGrouping(source, sourceGroup) } - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + predicate sinkGrouping(Node sink, string sinkGroup) { + any(Configuration config).sinkGrouping(sink, sinkGroup) } -} - -/** Holds if `n` can reach a sink. */ -private predicate directReach(PathNodeImpl n) { - n instanceof PathNodeSink or - n instanceof PathNodeSinkGroup or - directReach(n.getANonHiddenSuccessor()) -} - -/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ -private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } -/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { - n1.getANonHiddenSuccessor() = n2 and directReach(n2) + predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() } } -private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) +private import Impl as I +import I /** * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. */ -class PathNode instanceof PathNodeImpl { - PathNode() { reach(this) } - +class PathNode instanceof I::PathNode { /** Gets a textual representation of this element. */ final string toString() { result = super.toString() } @@ -3406,1548 +358,39 @@ class PathNode instanceof PathNodeImpl { } /** Gets the underlying `Node`. */ - final Node getNode() { super.getNodeEx().projectToNode() = result } + final Node getNode() { result = super.getNode() } /** Gets the `FlowState` of this node. */ - final FlowState getState() { result = super.getState() } + final FlowState getState() { result = getState(super.getState()) } /** Gets the associated configuration. */ - final Configuration getConfiguration() { result = super.getConfiguration() } + final Configuration getConfiguration() { result = getConfig(super.getState()) } /** Gets a successor of this node, if any. */ - final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } + final PathNode getASuccessor() { result = super.getASuccessor() } /** Holds if this node is a source. */ final predicate isSource() { super.isSource() } /** Holds if this node is a grouping of source nodes. */ - final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group, _) } + final predicate isSourceGroup(string group) { super.isSourceGroup(group) } /** Holds if this node is a grouping of sink nodes. */ - final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group, _) } + final predicate isSinkGroup(string group) { super.isSinkGroup(group) } } -/** - * Provides the query predicates needed to include a graph in a path-problem query. - */ -module PathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } - - /** Holds if `n` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode n, string key, string val) { - key = "semmle.label" and val = n.toString() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { - Subpaths::subpaths(arg, par, ret, out) - } -} - -/** - * An intermediate flow graph node. This is a triple consisting of a `Node`, - * a `CallContext`, and a `Configuration`. - */ -private class PathNodeMid extends PathNodeImpl, TPathNodeMid { - NodeEx node; - FlowState state; - CallContext cc; - SummaryCtx sc; - AccessPath ap; - Configuration config; - - PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - SummaryCtx getSummaryCtx() { result = sc } - - AccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - private PathNodeMid getSuccMid() { - pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx(), result.getAp()) and - result.getConfiguration() = unbindConf(this.getConfiguration()) - } - - override PathNodeImpl getASuccessorImpl() { - // an intermediate step to another intermediate node - result = this.getSuccMid() - or - // a final step to a sink - result = this.getSuccMid().projectToSink() - } - - override predicate isSource() { - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - } - - predicate isAtSink() { - sinkNode(node, state, config) and - ap instanceof AccessPathNil and - if hasSinkCallCtx(config) - then - // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` - // is exactly what we need to check. This also implies - // `sc instanceof SummaryCtxNone`. - // For `FeatureEqualSourceSinkCallContext` the initial call context was - // set to `CallContextSomeCall` and jumps are disallowed, so - // `cc instanceof CallContextNoCall` never holds. On the other hand, - // in this case there's never any need to enter a call except to identify - // a summary, so the condition in `pathIntoCallable` enforces this, which - // means that `sc instanceof SummaryCtxNone` holds if and only if we are - // in the call context of the source. - sc instanceof SummaryCtxNone or - cc instanceof CallContextNoCall - else any() - } - - PathNodeSink projectToSink() { - this.isAtSink() and - result.getNodeEx() = node and - result.getState() = state and - result.getConfiguration() = unbindConf(config) - } -} - -/** - * A flow graph node corresponding to a sink. This is disjoint from the - * intermediate nodes in order to uniquely correspond to a given sink by - * excluding the `CallContext`. - */ -private class PathNodeSink extends PathNodeImpl, TPathNodeSink { - NodeEx node; - FlowState state; - Configuration config; - - PathNodeSink() { this = TPathNodeSink(node, state, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result = TPathNodeSinkGroup(this.getSinkGroup(), config) - } - - override predicate isSource() { sourceNode(node, state, config) } - - string getSinkGroup() { config.sinkGrouping(node.asNode(), result) } -} - -private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { - string sourceGroup; - Configuration config; - - PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result.getSourceGroup() = sourceGroup and - result.getConfiguration() = config - } - - override predicate isSource() { none() } - - override string toString() { result = sourceGroup } - - override 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 - } -} - -private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { - string sinkGroup; - Configuration config; - - PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { none() } - - override predicate isSource() { none() } - - override string toString() { result = sinkGroup } - - override 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 - } -} - -private predicate pathNode( - PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, - Configuration conf, LocalCallContext localCC -) { - midnode = mid.getNodeEx() and - state = mid.getState() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - midnode.getEnclosingCallable()) and - ap = mid.getAp() -} - -/** - * Holds if data may flow from `mid` to `node`. The last step in or out of - * a callable is recorded by `cc`. - */ -pragma[assume_small_delta] -pragma[nomagic] -private predicate pathStep( - PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap -) { - exists(NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC | - pathNode(mid, midnode, state0, cc, sc, ap, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, true, _, conf, localCC) - ) - or - exists( - AccessPath ap0, NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC - | - pathNode(mid, midnode, state0, cc, sc, ap0, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), conf, - localCC) and - ap0 instanceof AccessPathNil - ) - or - jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - ap = mid.getAp() - or - additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, mid.getConfiguration()) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - pathIntoCallable(mid, node, state, _, cc, sc, _, _) and ap = mid.getAp() - or - pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone - or - pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() -} - -pragma[nomagic] -private predicate pathReadStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - tc = ap0.getHead() and - Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -pragma[nomagic] -private predicate pathStoreStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -private predicate pathOutOfCallable0( - PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa, - Configuration config -) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - apa = mid.getAp().getApprox() and - config = mid.getConfiguration() -} - -pragma[nomagic] -private predicate pathOutOfCallable1( - PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPathApprox apa, Configuration config -) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - pathOutOfCallable0(mid, pos, state, innercc, apa, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() +private predicate hasFlow(Node source, Node sink, Configuration config) { + exists(PathNode source0, PathNode sink0 | + hasFlowPath(source0, sink0, config) and + source0.getNode() = source and + sink0.getNode() = sink ) } -pragma[noinline] -private NodeEx getAnOutNodeFlow( - ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config -) { - result.asNode() = kind.getAnOutNode(call) and - Stage5::revFlow(result, _, apa, config) +private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) { + hasFlowPath(source, sink) and source.getConfiguration() = config } -/** - * Holds if data may flow from `mid` to `out`. The last step of this path - * is a return from a callable and is recorded by `cc`, if needed. - */ -pragma[noinline] -private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { - exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | - pathOutOfCallable1(mid, call, kind, state, cc, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} +private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) } -/** - * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. - */ -pragma[noinline] -private predicate pathIntoArg( - PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(ArgNodeEx arg, ArgumentPosition apos | - pathNode(mid, arg, state, cc, _, ap, config, _) and - arg.asNode().(ArgNode).argumentOf(call, apos) and - apa = ap.getApprox() and - parameterMatch(ppos, apos) - ) -} - -pragma[nomagic] -private predicate parameterCand( - DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::revFlow(p, _, apa, config) and - p.isParameterOf(callable, pos) - ) -} - -pragma[nomagic] -private predicate pathIntoCallable0( - PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, AccessPath ap, Configuration config -) { - exists(AccessPathApprox apa | - pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - callable = resolveCall(call, outercc) and - parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa), - pragma[only_bind_into](config)) - ) -} - -/** - * Holds if data may flow from `mid` to `p` through `call`. The contexts - * before and after entering the callable are `outercc` and `innercc`, - * respectively. - */ -pragma[nomagic] -private predicate pathIntoCallable( - PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, - SummaryCtx sc, DataFlowCall call, Configuration config -) { - exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | - pathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - ( - sc = TSummaryCtxSome(p, state, ap) - or - not exists(TSummaryCtxSome(p, state, ap)) and - sc = TSummaryCtxNone() and - // When the call contexts of source and sink needs to match then there's - // never any reason to enter a callable except to find a summary. See also - // the comment in `PathNodeMid::isAtSink`. - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) -} - -/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ -pragma[nomagic] -private predicate paramFlowsThrough( - ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, - AccessPathApprox apa, Configuration config -) { - exists(RetNodeEx ret | - pathNode(_, ret, state, cc, sc, ap, config, _) and - kind = ret.getKind() and - apa = ap.getApprox() and - parameterFlowThroughAllowed(sc.getParamNode(), kind) - ) -} - -pragma[nomagic] -private predicate pathThroughCallable0( - DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(CallContext innercc, SummaryCtx sc | - pathIntoCallable(mid, _, _, cc, innercc, sc, call, config) and - paramFlowsThrough(kind, state, innercc, sc, ap, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` through a callable to the node `out`. - * The context `cc` is restored to its value prior to entering the callable. - */ -pragma[noinline] -private predicate pathThroughCallable( - PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap -) { - exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config | - pathThroughCallable0(call, mid, kind, state, cc, ap, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -private module Subpaths { - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths01( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - exists(Configuration config | - pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and - pathIntoCallable(arg, par, _, _, innercc, sc, _, config) and - paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, - pragma[only_bind_into](apout), _, unbindConf(config)) and - not arg.isHidden() - ) - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `sout`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths02( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and - out.asNode() = kind.getAnOutNode(_) - } - - pragma[nomagic] - private Configuration getPathNodeConf(PathNodeImpl n) { result = n.getConfiguration() } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple. - */ - pragma[nomagic] - private predicate subpaths03( - PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, AccessPath apout - ) { - exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | - subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and - pathNode(ret, retnode, sout, innercc, sc, apout, unbindConf(getPathNodeConf(arg)), _) and - kind = retnode.getKind() - ) - } - - private PathNodeImpl localStepToHidden(PathNodeImpl n) { - n.getASuccessorImpl() = result and - result.isHidden() and - exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | - localFlowBigStep(n1, _, n2, _, _, _, _, _) or - store(n1, _, n2, _, _) or - readSet(n1, _, n2, _) - ) - } - - pragma[nomagic] - private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { - succ = pred.getANonHiddenSuccessor() and - succNode = succ.getNodeEx() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { - exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | - pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and - subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and - hasSuccessor(pragma[only_bind_into](arg), par, p) and - not ret.isHidden() and - pathNode(out0, o, sout, _, _, apout, _, _) - | - out = out0 or out = out0.projectToSink() - ) - } - - /** - * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. - */ - predicate retReach(PathNodeImpl n) { - exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) - or - exists(PathNodeImpl mid | - retReach(mid) and - n.getANonHiddenSuccessor() = mid and - not subpaths(_, mid, _, _) - ) - } -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -private predicate hasFlowPath( - PathNodeImpl flowsource, PathNodeImpl flowsink, Configuration configuration -) { - flowsource.isFlowSource() and - flowsource.getConfiguration() = configuration and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.isFlowSink() -} - -private predicate flowsTo( - PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink, - Configuration configuration -) { - flowsource.isSource() and - flowsource.getConfiguration() = configuration and - flowsource.getNodeEx().asNode() = source and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.getNodeEx().asNode() = sink -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -predicate flowsTo(Node source, Node sink, Configuration configuration) { - flowsTo(_, _, source, sink, configuration) -} - -private predicate finalStats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples -) { - fwd = true and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and - tuples = count(PathNodeImpl pn) - or - fwd = false and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and - tuples = count(PathNode pn) -} - -/** - * INTERNAL: Only for debugging. - * - * Calculates per-stage metrics for data flow. - */ -predicate stageStats( - int n, string stage, int nodes, int fields, int conscand, int states, int tuples, - Configuration config -) { - stage = "1 Fwd" and - n = 10 and - Stage1::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "1 Rev" and - n = 15 and - Stage1::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Fwd" and - n = 20 and - Stage2::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Rev" and - n = 25 and - Stage2::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Fwd" and - n = 30 and - Stage3::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Rev" and - n = 35 and - Stage3::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Fwd" and - n = 40 and - Stage4::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Rev" and - n = 45 and - Stage4::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Fwd" and - n = 50 and - Stage5::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Rev" and - n = 55 and - Stage5::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) - or - stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) -} - -private module FlowExploration { - private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - jumpStep(node1, node2, config) - or - additionalJumpStep(node1, node2, config) - or - additionalJumpStateStep(node1, _, node2, _, config) - or - // flow into callable - viableParamArgEx(_, node2, node1) - or - // flow out of a callable - viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) - | - c1 = node1.getEnclosingCallable() and - c2 = node2.getEnclosingCallable() and - c1 != c2 - ) - } - - private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSource(n) or config.isSource(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSrc(mid, config) and callableStep(mid, c, config) - ) - } - - private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSink(n) or config.isSink(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSink(mid, config) and callableStep(c, mid, config) - ) - } - - private newtype TCallableExt = - TCallable(DataFlowCallable c, Configuration config) { - interestingCallableSrc(c, config) or - interestingCallableSink(c, config) - } or - TCallableSrc() or - TCallableSink() - - private predicate callableExtSrc(TCallableSrc src) { any() } - - private predicate callableExtSink(TCallableSink sink) { any() } - - private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { - exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | - callableStep(c1, c2, config) and - ce1 = TCallable(c1, pragma[only_bind_into](config)) and - ce2 = TCallable(c2, pragma[only_bind_into](config)) - ) - or - exists(Node n, Configuration config | - ce1 = TCallableSrc() and - (config.isSource(n) or config.isSource(n, _)) and - ce2 = TCallable(getNodeEnclosingCallable(n), config) - ) - or - exists(Node n, Configuration config | - ce2 = TCallableSink() and - (config.isSink(n) or config.isSink(n, _)) and - ce1 = TCallable(getNodeEnclosingCallable(n), config) - ) - } - - private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { - callableExtStepFwd(ce2, ce1) - } - - private int distSrcExt(TCallableExt c) = - shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) - - private int distSinkExt(TCallableExt c) = - shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) - - private int distSrc(DataFlowCallable c, Configuration config) { - result = distSrcExt(TCallable(c, config)) - 1 - } - - private int distSink(DataFlowCallable c, Configuration config) { - result = distSinkExt(TCallable(c, config)) - 1 - } - - private newtype TPartialAccessPath = - TPartialNil(DataFlowType t) or - TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first - * element of the list and its length are tracked. If data flows from a source to - * a given node with a given `AccessPath`, this indicates the sequence of - * dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ - private class PartialAccessPath extends TPartialAccessPath { - abstract string toString(); - - TypedContent getHead() { this = TPartialCons(result, _) } - - int len() { - this = TPartialNil(_) and result = 0 - or - this = TPartialCons(_, result) - } - - DataFlowType getType() { - this = TPartialNil(result) - or - exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) - } - } - - private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { - override string toString() { - exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) - } - } - - private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { - override string toString() { - exists(TypedContent tc, int len | this = TPartialCons(tc, len) | - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private newtype TRevPartialAccessPath = - TRevPartialNil() or - TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `Content`s, but only the first - * element of the list and its length are tracked. - */ - private class RevPartialAccessPath extends TRevPartialAccessPath { - abstract string toString(); - - Content getHead() { this = TRevPartialCons(result, _) } - - int len() { - this = TRevPartialNil() and result = 0 - or - this = TRevPartialCons(_, result) - } - } - - private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { - override string toString() { result = "" } - } - - private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { - override string toString() { - exists(Content c, int len | this = TRevPartialCons(c, len) | - if len = 1 - then result = "[" + c.toString() + "]" - else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private predicate relevantState(FlowState state) { - sourceNode(_, state, _) or - sinkNode(_, state, _) or - additionalLocalStateStep(_, state, _, _, _) or - additionalLocalStateStep(_, _, _, state, _) or - additionalJumpStateStep(_, state, _, _, _) or - additionalJumpStateStep(_, _, _, state, _) - } - - private newtype TSummaryCtx1 = - TSummaryCtx1None() or - TSummaryCtx1Param(ParamNodeEx p) - - private newtype TSummaryCtx2 = - TSummaryCtx2None() or - TSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TSummaryCtx3 = - TSummaryCtx3None() or - TSummaryCtx3Some(PartialAccessPath ap) - - private newtype TRevSummaryCtx1 = - TRevSummaryCtx1None() or - TRevSummaryCtx1Some(ReturnPosition pos) - - private newtype TRevSummaryCtx2 = - TRevSummaryCtx2None() or - TRevSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TRevSummaryCtx3 = - TRevSummaryCtx3None() or - TRevSummaryCtx3Some(RevPartialAccessPath ap) - - private newtype TPartialPathNode = - TPartialPathNodeFwd( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = TPartialNil(node.getDataFlowType()) and - exists(config.explorationLimit()) - or - partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap, config) and - distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() - } or - TPartialPathNodeRev( - NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, - RevPartialAccessPath ap, Configuration config - ) { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() and - exists(config.explorationLimit()) - or - revPartialPathStep(_, node, state, sc1, sc2, sc3, ap, config) and - not clearsContentEx(node, ap.getHead()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead()) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() - } - - pragma[nomagic] - private predicate partialPathNodeMk0( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap, config) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - not clearsContentEx(node, ap.getHead().getContent()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead().getContent()) - ) and - if node.asNode() instanceof CastingNode - then compatibleTypes(node.getDataFlowType(), ap.getType()) - else any() - } - - /** - * A `Node` augmented with a call context, an access path, and a configuration. - */ - class PartialPathNode extends TPartialPathNode { - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() - } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - /** Gets the underlying `Node`. */ - final Node getNode() { this.getNodeEx().projectToNode() = result } - - FlowState getState() { none() } - - private NodeEx getNodeEx() { - result = this.(PartialPathNodeFwd).getNodeEx() or - result = this.(PartialPathNodeRev).getNodeEx() - } - - /** Gets the associated configuration. */ - Configuration getConfiguration() { none() } - - /** Gets a successor of this node, if any. */ - PartialPathNode getASuccessor() { none() } - - /** - * Gets the approximate distance to the nearest source measured in number - * of interprocedural steps. - */ - int getSourceDistance() { - result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - /** - * Gets the approximate distance to the nearest sink measured in number - * of interprocedural steps. - */ - int getSinkDistance() { - result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - private string ppAp() { - exists(string s | - s = this.(PartialPathNodeFwd).getAp().toString() or - s = this.(PartialPathNodeRev).getAp().toString() - | - if s = "" then result = "" else result = " " + s - ) - } - - private string ppCtx() { - result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" - } - - /** Holds if this is a source in a forward-flow path. */ - predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } - - /** Holds if this is a sink in a reverse-flow path. */ - predicate isRevSink() { this.(PartialPathNodeRev).isSink() } - } - - /** - * Provides the query predicates needed to include a graph in a path-problem query. - */ - module PartialPathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } - } - - private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { - NodeEx node; - FlowState state; - CallContext cc; - TSummaryCtx1 sc1; - TSummaryCtx2 sc2; - TSummaryCtx3 sc3; - PartialAccessPath ap; - Configuration config; - - PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - TSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TSummaryCtx3 getSummaryCtx3() { result = sc3 } - - PartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeFwd getASuccessor() { - partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp(), - result.getConfiguration()) - } - - predicate isSource() { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap instanceof TPartialNil - } - } - - private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { - NodeEx node; - FlowState state; - TRevSummaryCtx1 sc1; - TRevSummaryCtx2 sc2; - TRevSummaryCtx3 sc3; - RevPartialAccessPath ap; - Configuration config; - - PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } - - RevPartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeRev getASuccessor() { - revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), - this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp(), this.getConfiguration()) - } - - predicate isSink() { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() - } - } - - private predicate partialPathStep( - PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, - TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and - ( - localFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - ) - or - jumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - partialPathStoreStep(mid, _, _, node, ap) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(PartialAccessPath ap0, TypedContent tc | - partialPathReadStep(mid, ap0, tc, node, cc, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsFwd(ap, tc, ap0, config) - ) - or - partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap, config) - or - partialPathOutOfCallable(mid, node, state, cc, ap, config) and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() - or - partialPathThroughCallable(mid, node, state, cc, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - bindingset[result, i] - private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } - - pragma[inline] - private predicate partialPathStoreStep( - PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, - PartialAccessPath ap2 - ) { - exists(NodeEx midNode, DataFlowType contentType | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - store(midNode, tc, node, contentType, mid.getConfiguration()) and - ap2.getHead() = tc and - ap2.len() = unbindInt(ap1.len() + 1) and - compatibleTypes(ap1.getType(), contentType) - ) - } - - pragma[nomagic] - private predicate apConsFwd( - PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeFwd mid | - partialPathStoreStep(mid, ap1, tc, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate partialPathReadStep( - PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc, - Configuration config - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and - ap.getHead() = tc and - pragma[only_bind_into](config) = mid.getConfiguration() and - cc = mid.getCallContext() - ) - } - - private predicate partialPathOutOfCallable0( - PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, - PartialAccessPath ap, Configuration config - ) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - ap = mid.getAp() and - config = mid.getConfiguration() - } - - pragma[nomagic] - private predicate partialPathOutOfCallable1( - PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - partialPathOutOfCallable0(mid, pos, state, innercc, ap, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) - } - - private predicate partialPathOutOfCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(ReturnKindExt kind, DataFlowCall call | - partialPathOutOfCallable1(mid, call, kind, state, cc, ap, config) - | - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[noinline] - private predicate partialPathIntoArg( - PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ArgNode arg, ArgumentPosition apos | - arg = mid.getNodeEx().asNode() and - state = mid.getState() and - cc = mid.getCallContext() and - arg.argumentOf(call, apos) and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate partialPathIntoCallable0( - PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - partialPathIntoArg(mid, pos, state, outercc, call, ap, config) and - callable = resolveCall(call, outercc) - } - - private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, - CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ParameterPosition pos, DataFlowCallable callable | - partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - sc1 = TSummaryCtx1Param(p) and - sc2 = TSummaryCtx2Some(state) and - sc3 = TSummaryCtx3Some(ap) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) - } - - pragma[nomagic] - private predicate paramFlowsThroughInPartialPath( - ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeFwd mid, RetNodeEx ret | - mid.getNodeEx() = ret and - kind = ret.getKind() and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() and - ap = mid.getAp() - ) - } - - pragma[noinline] - private predicate partialPathThroughCallable0( - DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | - partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _, config) and - paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap, config) - ) - } - - private predicate partialPathThroughCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ReturnKindExt kind | - partialPathThroughCallable0(call, mid, kind, state, cc, ap, config) and - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[nomagic] - private predicate revPartialPathStep( - PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, - TRevSummaryCtx3 sc3, RevPartialAccessPath ap, Configuration config - ) { - localFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - jumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - revPartialPathReadStep(mid, _, _, node, ap) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(RevPartialAccessPath ap0, Content c | - revPartialPathStoreStep(mid, ap0, c, node, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsRev(ap, c, ap0, config) - ) - or - exists(ParamNodeEx p | - mid.getNodeEx() = p and - viableParamArgEx(_, p, node) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - or - exists(ReturnPosition pos | - revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap, config) and - pos = getReturnPosition(node.asNode()) - ) - or - revPartialPathThroughCallable(mid, node, state, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - pragma[inline] - private predicate revPartialPathReadStep( - PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, - RevPartialAccessPath ap2 - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - read(node, c, midNode, mid.getConfiguration()) and - ap2.getHead() = c and - ap2.len() = unbindInt(ap1.len() + 1) - ) - } - - pragma[nomagic] - private predicate apConsRev( - RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeRev mid | - revPartialPathReadStep(mid, ap1, c, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathStoreStep( - PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config - ) { - exists(NodeEx midNode, TypedContent tc | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - store(node, tc, midNode, _, config) and - ap.getHead() = c and - config = mid.getConfiguration() and - tc.getContent() = c - ) - } - - pragma[nomagic] - private predicate revPartialPathIntoReturn( - PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, - TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap, - Configuration config - ) { - exists(NodeEx out | - mid.getNodeEx() = out and - mid.getState() = state and - viableReturnPosOutEx(call, pos, out) and - sc1 = TRevSummaryCtx1Some(pos) and - sc2 = TRevSummaryCtx2Some(state) and - sc3 = TRevSummaryCtx3Some(ap) and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathFlowsThrough( - ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, - TRevSummaryCtx3Some sc3, RevPartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | - mid.getNodeEx() = p and - mid.getState() = state and - p.getPosition() = ppos and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable0( - DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, - RevPartialAccessPath ap, Configuration config - ) { - exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | - revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _, config) and - revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap, config) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ArgumentPosition pos | - revPartialPathThroughCallable0(call, mid, pos, state, ap, config) and - node.asNode().(ArgNode).argumentOf(call, pos) - ) - } -} - -import FlowExploration - -private predicate partialFlow( - PartialPathNode source, PartialPathNode node, Configuration configuration -) { - source.getConfiguration() = configuration and - source.isFwdSource() and - node = source.getASuccessor+() -} - -private predicate revPartialFlow( - PartialPathNode node, PartialPathNode sink, Configuration configuration -) { - sink.getConfiguration() = configuration and - sink.isRevSink() and - node.getASuccessor+() = sink -} +predicate flowsTo = hasFlow/3; diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll index 5d3becc8078..f8c56d41d8d 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll @@ -3,15 +3,18 @@ private import DataFlowImplSpecific::Public import Cached module DataFlowImplCommonPublic { - /** A state value to track during data flow. */ - class FlowState = string; + /** Provides `FlowState = string`. */ + module FlowStateString { + /** A state value to track during data flow. */ + class FlowState = string; - /** - * The default state, which is used when the state is unspecified for a source - * or a sink. - */ - class FlowStateEmpty extends FlowState { - FlowStateEmpty() { this = "" } + /** + * The default state, which is used when the state is unspecified for a source + * or a sink. + */ + class FlowStateEmpty extends FlowState { + FlowStateEmpty() { this = "" } + } } private newtype TFlowFeature = diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForOnActivityResult.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForOnActivityResult.qll index 1b969756b09..e6bdc74cceb 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForOnActivityResult.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForOnActivityResult.qll @@ -1,16 +1,16 @@ /** - * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from - * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed - * through the `Configuration` class. This file exists in several identical - * copies, allowing queries to use multiple `Configuration` classes that depend - * on each other without introducing mutual recursion among those configurations. + * DEPRECATED: Use `Make` and `MakeWithState` instead. + * + * Provides a `Configuration` class backwards-compatible interface to the data + * flow library. */ private import DataFlowImplCommon private import DataFlowImplSpecific::Private import DataFlowImplSpecific::Public +private import DataFlowImpl import DataFlowImplCommonPublic +import FlowStateString /** * A configuration of interprocedural data flow analysis. This defines @@ -144,6 +144,10 @@ abstract class Configuration extends string { * - `FeatureEqualSourceSinkCallContext`: * Implies both of the above and additionally ensures that the entire flow * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. */ FlowFeature getAFeature() { none() } @@ -156,7 +160,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from `source` to `sink` for this configuration. */ - predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } + predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) } /** * Holds if data may flow from `source` to `sink` for this configuration. @@ -169,9 +173,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from some source to `sink` for this configuration. */ - predicate hasFlowTo(Node sink) { - sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode() - } + predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) } /** * Holds if data may flow from some source to `sink` for this configuration. @@ -179,10 +181,12 @@ abstract class Configuration extends string { predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } /** + * DEPRECATED: Use `FlowExploration` instead. + * * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` * measured in approximate number of interprocedural steps. */ - int explorationLimit() { none() } + deprecated int explorationLimit() { none() } /** * Holds if hidden nodes should be included in the data flow graph. @@ -191,49 +195,6 @@ abstract class Configuration extends string { * is not visualized (for example in a `path-problem` query). */ predicate includeHiddenNodes() { none() } - - /** - * Holds if there is a partial data flow path from `source` to `node`. The - * approximate distance between `node` and the closest source is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards sink definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sources is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - */ - final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { - partialFlow(source, node, this) and - dist = node.getSourceDistance() - } - - /** - * Holds if there is a partial data flow path from `node` to `sink`. The - * approximate distance between `node` and the closest sink is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards source definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sinks is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - * - * Note that reverse flow has slightly lower precision than the corresponding - * forward flow, as reverse flow disregards type pruning among other features. - */ - final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { - revPartialFlow(node, sink, this) and - dist = node.getSinkDistance() - } } /** @@ -263,90 +224,6 @@ abstract private class ConfigurationRecursionPrevention extends Configuration { } } -private newtype TNodeEx = - TNodeNormal(Node n) or - TNodeImplicitRead(Node n, boolean hasRead) { - any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true] - } - -private class NodeEx extends TNodeEx { - string toString() { - result = this.asNode().toString() - or - exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") - } - - Node asNode() { this = TNodeNormal(result) } - - predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } - - Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } - - pragma[nomagic] - private DataFlowCallable getEnclosingCallable0() { - nodeEnclosingCallable(this.projectToNode(), result) - } - - pragma[inline] - DataFlowCallable getEnclosingCallable() { - pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) - } - - pragma[nomagic] - private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } - - pragma[inline] - DataFlowType getDataFlowType() { - pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) - } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -private class ArgNodeEx extends NodeEx { - ArgNodeEx() { this.asNode() instanceof ArgNode } -} - -private class ParamNodeEx extends NodeEx { - ParamNodeEx() { this.asNode() instanceof ParamNode } - - predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { - this.asNode().(ParamNode).isParameterOf(c, pos) - } - - ParameterPosition getPosition() { this.isParameterOf(_, result) } -} - -private class RetNodeEx extends NodeEx { - RetNodeEx() { this.asNode() instanceof ReturnNodeExt } - - ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } - - ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } -} - -private predicate inBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierIn(n) - | - config.isSource(n) or config.isSource(n, _) - ) -} - -private predicate outBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierOut(n) - | - config.isSink(n) or config.isSink(n, _) - ) -} - /** A bridge class to access the deprecated `isBarrierGuard`. */ private class BarrierGuardGuardedNodeBridge extends Unit { abstract predicate guardedNode(Node n, Configuration config); @@ -370,3019 +247,94 @@ private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { } } -pragma[nomagic] -private predicate fullBarrier(NodeEx node, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n) +private FlowState relevantState(Configuration config) { + config.isSource(_, result) or + config.isSink(_, result) or + config.isBarrier(_, result) or + config.isAdditionalFlowStep(_, result, _, _) or + config.isAdditionalFlowStep(_, _, _, result) +} + +private newtype TConfigState = + TMkConfigState(Configuration config, FlowState state) { + state = relevantState(config) or state instanceof FlowStateEmpty + } + +private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) } + +private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) } + +private predicate singleConfiguration() { 1 = strictcount(Configuration c) } + +private module Config implements FullStateConfigSig { + class FlowState = TConfigState; + + predicate isSource(Node source, FlowState state) { + getConfig(state).isSource(source, getState(state)) or - config.isBarrierIn(n) and - not config.isSource(n) and - not config.isSource(n, _) + getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty + } + + predicate isSink(Node sink, FlowState state) { + getConfig(state).isSink(sink, getState(state)) or - config.isBarrierOut(n) and - not config.isSink(n) and - not config.isSink(n, _) + getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty + } + + predicate isBarrier(Node node) { none() } + + predicate isBarrier(Node node, FlowState state) { + getConfig(state).isBarrier(node, getState(state)) or + getConfig(state).isBarrier(node) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state)) + } + + predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) } + + predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) } + + predicate isAdditionalFlowStep(Node node1, Node node2) { + singleConfiguration() and + any(Configuration config).isAdditionalFlowStep(node1, node2) + } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and + getConfig(state2) = getConfig(state1) or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config) - ) -} - -pragma[nomagic] -private predicate stateBarrier(NodeEx node, FlowState state, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n, state) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config) - ) -} - -pragma[nomagic] -private predicate sourceNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSource(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSource(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -pragma[nomagic] -private predicate sinkNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSink(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSink(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -/** Provides the relevant barriers for a step from `node1` to `node2`. */ -pragma[inline] -private predicate stepFilter(NodeEx node1, NodeEx node2, Configuration config) { - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) -} - -/** - * Holds if data can flow in one local step from `node1` to `node2`. - */ -private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.asNode() = n and - node2.isImplicitReadNode(n, false) and - not fullBarrier(node1, config) - ) -} - -/** - * Holds if the additional step from `node1` to `node2` does not jump between callables. - */ -private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.isImplicitReadNode(n, true) and - node2.asNode() = n and - not fullBarrier(node2, config) - ) -} - -private predicate additionalLocalStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) - ) -} - -/** - * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. - */ -private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if the additional step from `node1` to `node2` jumps between callables. - */ -private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate additionalJumpStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -pragma[nomagic] -private predicate readSet(NodeEx node1, ContentSet c, NodeEx node2, Configuration config) { - readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and - stepFilter(node1, node2, config) - or - exists(Node n | - node2.isImplicitReadNode(n, true) and - node1.isImplicitReadNode(n, _) and - config.allowImplicitRead(n, c) - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(ContentSet cs | - readSet(node1, cs, node2, config) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate clearsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - clearsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate expectsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - expectsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -pragma[nomagic] -private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } - -pragma[nomagic] -private predicate hasReadStep(Content c, Configuration config) { read(_, c, _, config) } - -pragma[nomagic] -private predicate store( - NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config -) { - store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), - contentType) and - hasReadStep(tc.getContent(), config) and - stepFilter(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { - viableReturnPosOut(call, pos, out.asNode()) -} - -pragma[nomagic] -private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { - viableParamArg(call, p.asNode(), arg.asNode()) -} - -/** - * Holds if field flow should be used for the given configuration. - */ -private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } - -private predicate hasSourceCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSourceCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate hasSinkCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSinkCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if flow from `p` to a return node of kind `kind` is allowed. - * - * We don't expect a parameter to return stored in itself, unless - * explicitly allowed - */ -bindingset[p, kind] -private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { - exists(ParameterPosition pos | p.isParameterOf(_, pos) | - not kind.(ParamUpdateReturnKind).getPosition() = pos - or - allowParameterReturnInSelfCached(p.asNode()) - ) -} - -private module Stage1 implements StageSig { - class Ap extends int { - // workaround for bad functionality-induced joins (happens when using `Unit`) - pragma[nomagic] - Ap() { this in [0 .. 1] and this < 1 } - } - - private class Cc = boolean; - - /* Begin: Stage 1 logic. */ - /** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `cc` records whether the node is reached through an - * argument in a call. - */ - private predicate fwdFlow(NodeEx node, Cc cc, Configuration config) { - sourceNode(node, _, config) and - if hasSourceCallCtx(config) then cc = true else cc = false - or - exists(NodeEx mid | fwdFlow(mid, cc, config) | - localFlowStep(mid, node, config) or - additionalLocalFlowStep(mid, node, config) or - additionalLocalStateStep(mid, _, node, _, config) - ) - or - exists(NodeEx mid | fwdFlow(mid, _, config) and cc = false | - jumpStep(mid, node, config) or - additionalJumpStep(mid, node, config) or - additionalJumpStateStep(mid, _, node, _, config) - ) - or - // store - exists(NodeEx mid | - useFieldFlow(config) and - fwdFlow(mid, cc, config) and - store(mid, _, node, _, config) - ) - or - // read - exists(ContentSet c | - fwdFlowReadSet(c, node, cc, config) and - fwdFlowConsCandSet(c, _, config) - ) - or - // flow into a callable - fwdFlowIn(_, _, _, node, config) and - cc = true - or - // flow out of a callable - fwdFlowOut(_, node, false, config) and - cc = false - or - // flow through a callable - exists(DataFlowCall call | - fwdFlowOutFromArg(call, node, config) and - fwdFlowIsEntered(call, cc, config) - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowIn( - DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p, Configuration config - ) { - // call context cannot help reduce virtual dispatch - fwdFlow(arg, cc, config) and - viableParamArgEx(call, p, arg) and - not fullBarrier(p, config) and - ( - cc = false - or - cc = true and - not reducedViableImplInCallContext(call, _, _) - ) - or - // call context may help reduce virtual dispatch - exists(DataFlowCallable target | - fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target, config) and - target = viableImplInSomeFwdFlowCallContextExt(call, config) and - cc = true - ) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - fwdFlowIn(call, _, cc, _, config) - } - - pragma[nomagic] - private predicate fwdFlowInReducedViableImplInSomeCallContext( - DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target, Configuration config - ) { - fwdFlow(arg, true, config) and - viableParamArgEx(call, p, arg) and - reducedViableImplInCallContext(call, _, _) and - target = p.getEnclosingCallable() and - not fullBarrier(p, config) - } - - /** - * Gets a viable dispatch target of `call` in the context `ctx`. This is - * restricted to those `call`s for which a context might make a difference, - * and to `ctx`s that are reachable in `fwdFlow`. - */ - pragma[nomagic] - private DataFlowCallable viableImplInSomeFwdFlowCallContextExt( - DataFlowCall call, Configuration config - ) { - exists(DataFlowCall ctx | - fwdFlowIsEntered(ctx, _, config) and - result = viableImplInCallContextExt(call, ctx) - ) - } - - private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) } - - pragma[nomagic] - private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc, Configuration config) { - exists(NodeEx mid | - fwdFlow(mid, cc, config) and - readSet(mid, c, node, config) - ) - } - - /** - * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - fwdFlow(mid, _, config) and - store(mid, tc, node, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `cs` may be interpreted in a read as the target of some store - * into `c`, in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCandSet(ContentSet cs, Content c, Configuration config) { - fwdFlowConsCand(c, config) and - c = cs.getAReadContent() - } - - pragma[nomagic] - private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { - exists(RetNodeEx ret | - fwdFlow(ret, cc, config) and - ret.getReturnPosition() = pos - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) { - exists(ReturnPosition pos | - fwdFlowReturnPosition(pos, cc, config) and - viableReturnPosOutEx(call, pos, out) and - not fullBarrier(out, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) { - fwdFlowOut(call, out, true, config) - } - - private predicate stateStepFwd(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1 | - additionalLocalStateStep(node1, state1, _, state2, config) or - additionalJumpStateStep(node1, state1, _, state2, config) - | - fwdFlow(node1, config) - ) - } - - private predicate fwdFlowState(FlowState state, Configuration config) { - sourceNode(_, state, config) - or - exists(FlowState state0 | - fwdFlowState(state0, config) and - stateStepFwd(state0, state, config) - ) - } - - /** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink. - */ - pragma[nomagic] - private predicate revFlow(NodeEx node, boolean toReturn, Configuration config) { - revFlow0(node, toReturn, config) and - fwdFlow(node, config) - } - - pragma[nomagic] - private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) { - exists(FlowState state | - fwdFlow(node, pragma[only_bind_into](config)) and - sinkNode(node, state, config) and - fwdFlowState(state, pragma[only_bind_into](config)) and - if hasSinkCallCtx(config) then toReturn = true else toReturn = false - ) - or - exists(NodeEx mid | revFlow(mid, toReturn, config) | - localFlowStep(node, mid, config) or - additionalLocalFlowStep(node, mid, config) or - additionalLocalStateStep(node, _, mid, _, config) - ) - or - exists(NodeEx mid | revFlow(mid, _, config) and toReturn = false | - jumpStep(node, mid, config) or - additionalJumpStep(node, mid, config) or - additionalJumpStateStep(node, _, mid, _, config) - ) - or - // store - exists(Content c | - revFlowStore(c, node, toReturn, config) and - revFlowConsCand(c, config) - ) - or - // read - exists(NodeEx mid, ContentSet c | - readSet(node, c, mid, config) and - fwdFlowConsCandSet(c, _, pragma[only_bind_into](config)) and - revFlow(mid, toReturn, pragma[only_bind_into](config)) - ) - or - // flow into a callable - revFlowIn(_, node, false, config) and - toReturn = false - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(pos, config) and - node.(RetNodeEx).getReturnPosition() = pos and - toReturn = true - ) - or - // flow through a callable - exists(DataFlowCall call | - revFlowInToReturn(call, node, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - /** - * Holds if `c` is the target of a read in the flow covered by `revFlow`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, ContentSet cs | - fwdFlow(node, pragma[only_bind_into](config)) and - readSet(node, cs, mid, config) and - fwdFlowConsCandSet(cs, c, pragma[only_bind_into](config)) and - revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) { - exists(NodeEx mid, TypedContent tc | - revFlow(mid, toReturn, pragma[only_bind_into](config)) and - fwdFlowConsCand(c, pragma[only_bind_into](config)) and - store(node, tc, mid, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `c` is the target of both a read and a store in the flow covered - * by `revFlow`. - */ - pragma[nomagic] - additional predicate revFlowIsReadAndStored(Content c, Configuration conf) { - revFlowConsCand(c, conf) and - revFlowStore(c, _, _, conf) - } - - pragma[nomagic] - additional predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config - ) { - fwdFlowReturnPosition(pos, _, config) and - viableReturnPosOutEx(call, pos, out) - } - - pragma[nomagic] - private predicate revFlowOut(ReturnPosition pos, Configuration config) { - exists(NodeEx out | - revFlow(out, _, config) and - viableReturnPosOutNodeCandFwd1(_, pos, out, config) - ) - } - - pragma[nomagic] - additional predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config - ) { - fwdFlowIn(call, arg, _, p, config) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate revFlowIn( - DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config - ) { - exists(ParamNodeEx p | - revFlow(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) { - revFlowIn(call, arg, true, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(NodeEx out | - revFlow(out, toReturn, config) and - fwdFlowOutFromArg(call, out, config) - ) - } - - private predicate stateStepRev(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - additionalLocalStateStep(node1, state1, node2, state2, config) or - additionalJumpStateStep(node1, state1, node2, state2, config) - | - revFlow(node1, _, pragma[only_bind_into](config)) and - revFlow(node2, _, pragma[only_bind_into](config)) and - fwdFlowState(state1, pragma[only_bind_into](config)) and - fwdFlowState(state2, pragma[only_bind_into](config)) - ) - } - - additional predicate revFlowState(FlowState state, Configuration config) { - exists(NodeEx node | - sinkNode(node, state, config) and - revFlow(node, _, pragma[only_bind_into](config)) and - fwdFlowState(state, pragma[only_bind_into](config)) - ) - or - exists(FlowState state0 | - revFlowState(state0, config) and - stateStepRev(state, state0, config) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Content c | - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - revFlow(node2, pragma[only_bind_into](config)) and - store(node1, tc, node2, contentType, config) and - c = tc.getContent() and - exists(ap1) - ) - } - - pragma[nomagic] - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) { - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - read(n1, c, n2, pragma[only_bind_into](config)) and - revFlow(n2, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, config) and - exists(ap) - } - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, _, pragma[only_bind_into](config)) and - exists(state) and - exists(ap) - } - - private predicate throughFlowNodeCand(NodeEx node, Configuration config) { - revFlow(node, true, config) and - fwdFlow(node, true, config) and - not inBarrier(node, config) and - not outBarrier(node, config) - } - - /** Holds if flow may return from `callable`. */ - pragma[nomagic] - private predicate returnFlowCallableNodeCand( - DataFlowCallable callable, ReturnKindExt kind, Configuration config - ) { - exists(RetNodeEx ret | - throughFlowNodeCand(ret, config) and - callable = ret.getEnclosingCallable() and - kind = ret.getKind() - ) - } - - /** - * Holds if flow may enter through `p` and reach a return node making `p` a - * candidate for the origin of a summary. - */ - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(DataFlowCallable c, ReturnKindExt kind | - throughFlowNodeCand(p, config) and - returnFlowCallableNodeCand(c, kind, config) and - p.getEnclosingCallable() = c and - exists(ap) and - parameterFlowThroughAllowed(p, kind) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - throughFlowNodeCand(ret, config) and - kind = ret.getKind() and - exists(argAp) and - exists(ap) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, boolean toReturn | - revFlow(arg, toReturn, config) and - revFlowInToReturn(call, arg, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, config)) and - fields = count(Content f0 | fwdFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | fwdFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, config)) and - fields = count(Content f0 | revFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | revFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | revFlow(n, b, config)) - } - /* End: Stage 1 logic. */ -} - -pragma[noinline] -private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - localFlowStep(node1, node2, config) -} - -pragma[noinline] -private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - additionalLocalFlowStep(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCand1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config -) { - Stage1::revFlow(out, config) and - Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, Configuration config -) { - exists(ReturnPosition pos | - viableReturnPosOutNodeCand1(call, pos, out, config) and - pos = ret.getReturnPosition() and - kind = pos.getKind() and - Stage1::revFlow(ret, config) and - not outBarrier(ret, config) and - not inBarrier(out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCand1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config -) { - Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and - Stage1::revFlow(arg, config) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config -) { - viableParamArgNodeCand1(call, p, arg, config) and - Stage1::revFlow(p, config) and - not outBarrier(arg, config) and - not inBarrier(p, config) -} - -/** - * Gets the amount of forward branching on the origin of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int branch(NodeEx n1, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n1, _, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) - ) -} - -/** - * Gets the amount of backward branching on the target of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int join(NodeEx n2, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n, _, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) - ) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. The - * `allowsFieldFlow` flag indicates whether the branching is within the limit - * specified by the configuration. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, ret, kind, out, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(ret, pragma[only_bind_into](config)) and - j = join(out, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. The `allowsFieldFlow` flag indicates whether - * the branching is within the limit specified by the configuration. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config -) { - flowIntoCallNodeCand1(call, arg, p, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(arg, pragma[only_bind_into](config)) and - j = join(p, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -private signature module StageSig { - class Ap; - - predicate revFlow(NodeEx node, Configuration config); - - predicate revFlowAp(NodeEx node, Ap ap, Configuration config); - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config); - - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config); - - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config); - - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ); - - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ); - - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config); -} - -private module MkStage { - class ApApprox = PrevStage::Ap; - - signature module StageParam { - class Ap; - - class ApNil extends Ap; - - bindingset[result, ap] - ApApprox getApprox(Ap ap); - - ApNil getApNil(NodeEx node); - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail); - - /** - * An approximation of `Content` that corresponds to the precision level of - * `Ap`, such that the mappings from both `Ap` and `Content` to this type - * are functional. - */ - class ApHeadContent; - - ApHeadContent getHeadContent(Ap ap); - - ApHeadContent projectToHeadContent(Content c); - - class ApOption; - - ApOption apNone(); - - ApOption apSome(Ap ap); - - class Cc; - - class CcCall extends Cc; - - // TODO: member predicate on CcCall - predicate matchesCall(CcCall cc, DataFlowCall call); - - class CcNoCall extends Cc; - - Cc ccNone(); - - CcCall ccSomeCall(); - - class LocalCc; - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc); - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ); - - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config - ); - - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config - ); - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config); - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType); - } - - module Stage implements StageSig { - import Param - - /* Begin: Stage logic. */ - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - private predicate revFlowApAlias(NodeEx node, ApApprox apa, Configuration config) { - PrevStage::revFlowAp(node, apa, config) - } - - pragma[nomagic] - private predicate flowIntoCallApa( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa, - Configuration config - ) { - flowIntoCall(call, arg, p, allowsFieldFlow, config) and - PrevStage::revFlowAp(p, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(arg, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowOutOfCallApa( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - ApApprox apa, Configuration config - ) { - flowOutOfCall(call, ret, kind, out, allowsFieldFlow, config) and - PrevStage::revFlowAp(out, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(ret, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowThroughOutOfCall( - DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, - ApApprox argApa, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa, pragma[only_bind_into](config)) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) and - matchesCall(ccc, call) - ) - } - - /** - * Holds if `node` is reachable with access path `ap` from a source in the - * configuration `config`. - * - * The call context `cc` records whether the node is reached through an - * argument in a call, and if so, `summaryCtx` and `argAp` record the - * corresponding parameter position and access path of that argument, respectively. - */ - pragma[nomagic] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa, config) and - PrevStage::revFlow(node, state, apa, config) and - filter(node, state, ap, config) - } - - pragma[inline] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, cc, summaryCtx, argAp, ap, _, config) - } - - pragma[nomagic] - private predicate fwdFlow0( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - sourceNode(node, state, config) and - (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and - argAp = apNone() and - summaryCtx = TParamNodeNone() and - ap = getApNil(node) and - apa = getApprox(ap) - or - exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | - fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0, config) and - localCc = getLocalCc(mid, cc) - | - localStep(mid, state0, node, state, true, _, config, localCc) and - ap = ap0 and - apa = apa0 - or - localStep(mid, state0, node, state, false, ap, config, localCc) and - ap0 instanceof ApNil and - apa = getApprox(ap) - ) - or - exists(NodeEx mid | - fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa, pragma[only_bind_into](config)) and - jumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(mid, state, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(mid, state0, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStateStep(mid, state0, node, state, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - // store - exists(TypedContent tc, Ap ap0 | - fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp, config) and - ap = apCons(tc, ap0) and - apa = getApprox(ap) - ) - or - // read - exists(Ap ap0, Content c | - fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp, config) and - fwdFlowConsCand(ap0, c, ap, config) and - apa = getApprox(ap) - ) - or - // flow into a callable - fwdFlowIn(_, node, state, _, cc, _, _, ap, apa, config) and - if PrevStage::parameterMayFlowThrough(node, apa, config) - then ( - summaryCtx = TParamNodeSome(node.asNode()) and - argAp = apSome(ap) - ) else ( - summaryCtx = TParamNodeNone() and argAp = apNone() - ) - or - // flow out of a callable - exists( - DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, - DataFlowCallable inner - | - fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa, config) and - flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa, config) and - inner = ret.getEnclosingCallable() and - cc = getCallContextReturn(inner, call, innercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - or - // flow through a callable - exists( - DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, ApApprox innerArgApa - | - fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowStore( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(DataFlowType contentType, ApApprox apa1 | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1, config) and - PrevStage::storeStepCand(node1, apa1, tc, node2, contentType, config) and - typecheckStore(ap1, contentType) - ) - } - - /** - * Holds if forward flow with access path `tail` reaches a store of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(TypedContent tc | - fwdFlowStore(_, tail, tc, _, _, _, _, _, config) and - tc.getContent() = c and - cons = apCons(tc, tail) - ) - } - - pragma[nomagic] - private predicate readStepCand( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - PrevStage::readStepCand(node1, c, node2, config) and - apc = projectToHeadContent(c) - } - - bindingset[node1, apc] - pragma[inline_late] - private predicate readStepCand0( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - readStepCand(node1, apc, c, node2, config) - } - - pragma[nomagic] - private predicate fwdFlowRead( - Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(ApHeadContent apc | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap, config) and - apc = getHeadContent(ap) and - readStepCand0(node1, apc, c, node2, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowIn( - DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, - ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa, Configuration config - ) { - exists(ArgNodeEx arg, boolean allowsFieldFlow | - fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa, config) and - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowRetFromArg( - RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, ApApprox argApa, - Ap ap, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - fwdFlow(pragma[only_bind_into](ret), state, ccc, - TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), - pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa), - pragma[only_bind_into](config)) and - kind = ret.getKind() and - parameterFlowThroughAllowed(summaryCtx, kind) and - argApa = getApprox(argAp) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) - ) - } - - pragma[inline] - private predicate fwdFlowThrough0( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, - Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa, config) and - fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp, config) - } - - pragma[nomagic] - private predicate fwdFlowThrough( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa, - config) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` - * and data might flow through the target callable and back out at `call`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered( - DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, - ParamNodeEx p, Ap ap, Configuration config - ) { - exists(ApApprox apa | - fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(p, apa, config) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate storeStepFwd( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config - ) { - fwdFlowStore(node1, ap1, tc, node2, _, _, _, _, config) and - ap2 = apCons(tc, ap1) and - fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _, config) - } - - private predicate readStepFwd( - NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config - ) { - fwdFlowRead(ap1, c, n1, n2, _, _, _, _, config) and - fwdFlowConsCand(ap1, c, ap2, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough0( - DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, - ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, - innerArgApa, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough( - RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, - Ap ap, Configuration config - ) { - exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | - returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa, config) and - pos = ret.getReturnPosition() and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowThroughIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap, - Configuration config - ) { - exists(ApApprox argApa | - flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), - allowsFieldFlow, argApa, pragma[only_bind_into](config)) and - fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa, - pragma[only_bind_into](config)) and - returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap, - pragma[only_bind_into](config)) and - if allowsFieldFlow = false then argAp instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowIntoCallAp( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap, - Configuration config - ) { - exists(ApApprox apa | - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - fwdFlow(arg, _, _, _, _, ap, apa, config) - ) - } - - pragma[nomagic] - private predicate flowOutOfCallAp( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, - Ap ap, Configuration config - ) { - exists(ApApprox apa | - flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa, config) and - fwdFlow(ret, _, _, _, _, ap, apa, config) and - pos = ret.getReturnPosition() - ) - } - - /** - * Holds if `node` with access path `ap` is part of a path from a source to a - * sink in the configuration `config`. - * - * The parameter `returnCtx` records whether (and how) the node must be returned - * from the enclosing callable in order to reach a sink, and if so, `returnAp` - * records the access path of the returned value. - */ - pragma[nomagic] - additional predicate revFlow( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - revFlow0(node, state, returnCtx, returnAp, ap, config) and - fwdFlow(node, state, _, _, _, ap, config) - } - - pragma[nomagic] - private predicate revFlow0( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, _, _, _, ap, config) and - sinkNode(node, state, config) and - ( - if hasSinkCallCtx(config) - then returnCtx = TReturnCtxNoFlowThrough() - else returnCtx = TReturnCtxNone() - ) and - returnAp = apNone() and - ap instanceof ApNil - or - exists(NodeEx mid, FlowState state0 | - localStep(node, state, mid, state0, true, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, ap, config) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap, pragma[only_bind_into](config)) and - localStep(node, pragma[only_bind_into](state), mid, state0, false, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, nil, pragma[only_bind_into](config)) and - ap instanceof ApNil - ) - or - exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, state, _, _, ap, config) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStep(node, mid, config) and - revFlow(pragma[only_bind_into](mid), state, _, _, nil, pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStateStep(node, state, mid, state0, config) and - revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil, - pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - // store - exists(Ap ap0, Content c | - revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp, config) and - revFlowConsCand(ap0, c, ap, config) - ) - or - // read - exists(NodeEx mid, Ap ap0 | - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - readStepFwd(node, ap, _, mid, ap0, config) - ) - or - // flow into a callable - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, state, TReturnCtxNone(), returnAp, ap, config) and - flowIntoCallAp(_, node, p, allowsFieldFlow, ap, config) and - (if allowsFieldFlow = false then ap instanceof ApNil else any()) and - returnCtx = TReturnCtxNone() - ) - or - // flow through a callable - exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, node, p, _, ap, innerReturnAp, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(_, node, pos, state, _, _, ap, config) and - if returnFlowsThrough(node, pos, state, _, _, _, ap, config) - then ( - returnCtx = TReturnCtxMaybeFlowThrough(pos) and - returnAp = apSome(ap) - ) else ( - returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() - ) - ) - } - - pragma[nomagic] - private predicate revFlowStore( - Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, - ReturnCtx returnCtx, ApOption returnAp, Configuration config - ) { - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - storeStepFwd(node, ap, tc, mid, ap0, config) and - tc.getContent() = c - } - - /** - * Holds if reverse flow with access path `tail` reaches a read of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(NodeEx mid, Ap tail0 | - revFlow(mid, _, _, _, tail, config) and - tail = pragma[only_bind_into](tail0) and - readStepFwd(_, cons, c, mid, tail0, config) - ) - } - - pragma[nomagic] - private predicate revFlowOut( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, - ApOption returnAp, Ap ap, Configuration config - ) { - exists(NodeEx out, boolean allowsFieldFlow | - revFlow(out, state, returnCtx, returnAp, ap, config) and - flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate revFlowParamToReturn( - ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap, Configuration config - ) { - revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), - pragma[only_bind_into](ap), pragma[only_bind_into](config)) and - parameterFlowThroughAllowed(p, pos.getKind()) and - PrevStage::parameterMayFlowThrough(p, getApprox(ap), config) - } - - pragma[nomagic] - private predicate revFlowThrough( - DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, - ApOption returnAp, Ap ap, Ap innerReturnAp, Configuration config - ) { - revFlowParamToReturn(p, state, pos, innerReturnAp, ap, config) and - revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned( - DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap, - Configuration config - ) { - exists(RetNodeEx ret, FlowState state, CcCall ccc | - revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap, config) and - returnFlowsThrough(ret, pos, state, ccc, _, _, ap, config) and - matchesCall(ccc, call) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Ap ap2, Content c | - PrevStage::storeStepCand(node1, _, tc, node2, contentType, config) and - revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _, config) and - revFlowConsCand(ap2, c, ap1, config) - ) - } - - predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(Ap ap1, Ap ap2 | - revFlow(node2, _, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and - readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _, - pragma[only_bind_into](config)) - ) - } - - additional predicate revFlow(NodeEx node, FlowState state, Configuration config) { - revFlow(node, state, _, _, _, config) - } - - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, _, _, ap, config) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, _, _, _, ap, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, Configuration config) { - revFlow(node, _, _, _, _, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, ap, config) - } - - private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepFwd(_, ap, tc, _, _, config) - } - - private predicate revConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepCand(_, ap, tc, _, _, config) - } - - private predicate validAp(Ap ap, Configuration config) { - revFlow(_, _, _, _, ap, config) and ap instanceof ApNil - or - exists(TypedContent head, Ap tail | - consCand(head, tail, config) and - ap = apCons(head, tail) - ) - } - - additional predicate consCand(TypedContent tc, Ap ap, Configuration config) { - revConsCand(tc, ap, config) and - validAp(ap, config) - } - - pragma[nomagic] - private predicate parameterFlowsThroughRev( - ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp, Configuration config - ) { - revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap, config) and - parameterFlowThroughAllowed(p, pos.getKind()) - } - - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(ReturnPosition pos | - returnFlowsThrough(_, pos, _, _, p, ap, _, config) and - parameterFlowsThroughRev(p, ap, pos, _, config) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - exists(ParamNodeEx p, ReturnPosition pos | - returnFlowsThrough(ret, pos, _, _, p, argAp, ap, config) and - parameterFlowsThroughRev(p, argAp, pos, ap, config) and - kind = pos.getKind() - ) - } - - pragma[nomagic] - private predicate revFlowThroughArg( - DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, - Ap ap, Configuration config - ) { - exists(ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp, config) - ) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | - revFlow(arg, state, returnCtx, returnAp, ap, config) and - revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, config)) and - fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and - states = count(FlowState state | fwdFlow(_, state, _, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | - fwdFlow(n, state, cc, summaryCtx, argAp, ap, config) - ) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, _, _, _, config)) and - fields = count(TypedContent f0 | consCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and - states = count(FlowState state | revFlow(_, state, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | - revFlow(n, state, returnCtx, retAp, ap, config) - ) - } - /* End: Stage logic. */ - } -} - -private module BooleanCallContext { - class Cc extends boolean { - Cc() { this in [true, false] } - } - - class CcCall extends Cc { - CcCall() { this = true } - } - - /** Holds if the call context may be `call`. */ - predicate matchesCall(CcCall cc, DataFlowCall call) { any() } - - class CcNoCall extends Cc { - CcNoCall() { this = false } - } - - Cc ccNone() { result = false } - - CcCall ccSomeCall() { result = true } - - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } -} - -private module Level1CallContext { - class Cc = CallContext; - - class CcCall = CallContextCall; - - pragma[inline] - predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } - - class CcNoCall = CallContextNoCall; - - Cc ccNone() { result instanceof CallContextAny } - - CcCall ccSomeCall() { result instanceof CallContextSomeCall } - - module NoLocalCallContext { - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSiteDispatch(call, c) - then result = TSpecificCall(call) - else result = TSomeCall() - } - } - - module LocalCallContext { - class LocalCc = LocalCallContext; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { - result = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - node.getEnclosingCallable()) - } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() - } - } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { - checkCallContextReturn(innercc, c, call) and - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() - } -} - -private module Stage2Param implements MkStage::StageParam { - private module PrevStage = Stage1; - - class Ap extends boolean { - Ap() { this in [true, false] } - } - - class ApNil extends Ap { - ApNil() { this = false } - } - - bindingset[result, ap] - PrevStage::Ap getApprox(Ap ap) { any() } - - ApNil getApNil(NodeEx node) { Stage1::revFlow(node, _) and exists(result) } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } - - class ApHeadContent = Unit; - - pragma[inline] - ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } - - ApHeadContent projectToHeadContent(Content c) { any() } - - class ApOption = BooleanOption; - - ApOption apNone() { result = TBooleanNone() } - - ApOption apSome(Ap ap) { result = TBooleanSome(ap) } - - import Level1CallContext - import NoLocalCallContext - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - ( - preservesValue = true and - localFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalStateStep(node1, state1, node2, state2, config) - ) and - exists(ap) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand1/6; - - predicate flowIntoCall = flowIntoCallNodeCand1/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - expectsContentEx(node, c) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - PrevStage::revFlowState(state, pragma[only_bind_into](config)) and - exists(ap) and - not stateBarrier(node, state, config) and - ( - notExpectsContent(node) - or - ap = true and - expectsContentCand(node, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage2 implements StageSig { - import MkStage::Stage -} - -pragma[nomagic] -private predicate flowOutOfCallNodeCand2( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -pragma[nomagic] -private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -private module LocalFlowBigStep { - /** - * A node where some checking is required, and hence the big-step relation - * is not allowed to step over. - */ - private class FlowCheckNode extends NodeEx { - FlowCheckNode() { - castNode(this.asNode()) or - clearsContentCached(this.asNode(), _) or - expectsContentCached(this.asNode(), _) - } - } - - /** - * Holds if `node` can be the first node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowEntry(NodeEx node, FlowState state, Configuration config) { - Stage2::revFlow(node, state, config) and - ( - sourceNode(node, state, config) - or - jumpStep(_, node, config) - or - additionalJumpStep(_, node, config) - or - additionalJumpStateStep(_, _, node, state, config) - or - node instanceof ParamNodeEx - or - node.asNode() instanceof OutNodeExt - or - Stage2::storeStepCand(_, _, _, node, _, config) - or - Stage2::readStepCand(_, _, node, config) - or - node instanceof FlowCheckNode - or - exists(FlowState s | - additionalLocalStateStep(_, s, node, state, config) and - s != state - ) - ) - } - - /** - * Holds if `node` can be the last node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowExit(NodeEx node, FlowState state, Configuration config) { - exists(NodeEx next | Stage2::revFlow(next, state, config) | - jumpStep(node, next, config) or - additionalJumpStep(node, next, config) or - flowIntoCallNodeCand2(_, node, next, _, config) or - flowOutOfCallNodeCand2(_, node, _, next, _, config) or - Stage2::storeStepCand(node, _, _, next, _, config) or - Stage2::readStepCand(node, _, next, config) - ) - or - exists(NodeEx next, FlowState s | Stage2::revFlow(next, s, config) | - additionalJumpStateStep(node, state, next, s, config) - or - additionalLocalStateStep(node, state, next, s, config) and - s != state - ) - or - Stage2::revFlow(node, state, config) and - node instanceof FlowCheckNode - or - sinkNode(node, state, config) - } - - pragma[noinline] - private predicate additionalLocalFlowStepNodeCand2( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, Configuration config - ) { - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 and - Stage2::revFlow(node1, pragma[only_bind_into](state1), false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false, - pragma[only_bind_into](config)) - or - additionalLocalStateStep(node1, state1, node2, state2, config) and - Stage2::revFlow(node1, state1, false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, state2, false, pragma[only_bind_into](config)) - } - - /** - * Holds if the local path from `node1` to `node2` is a prefix of a maximal - * subsequence of local flow steps in a dataflow path. - * - * This is the transitive closure of `[additional]localFlowStep` beginning - * at `localFlowEntry`. - */ - pragma[nomagic] - private predicate localFlowStepPlus( - NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, - Configuration config, LocalCallContext cc - ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and - ( - localFlowEntry(node1, pragma[only_bind_into](state), pragma[only_bind_into](config)) and - ( - localFlowStepNodeCand1(node1, node2, config) and - preservesValue = true and - t = node1.getDataFlowType() and // irrelevant dummy value - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - or - additionalLocalFlowStepNodeCand2(node1, state, node2, state, config) and - preservesValue = false and - t = node2.getDataFlowType() - ) and - node1 != node2 and - cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) - or - exists(NodeEx mid | - localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, - pragma[only_bind_into](config), cc) and - localFlowStepNodeCand1(mid, node2, config) and - not mid instanceof FlowCheckNode and - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - ) - or - exists(NodeEx mid | - localFlowStepPlus(node1, state, mid, _, _, pragma[only_bind_into](config), cc) and - additionalLocalFlowStepNodeCand2(mid, state, node2, state, config) and - not mid instanceof FlowCheckNode and - preservesValue = false and - t = node2.getDataFlowType() - ) - ) - } - - /** - * Holds if `node1` can step to `node2` in one or more local steps and this - * path can occur as a maximal subsequence of local steps in a dataflow path. - */ - pragma[nomagic] - predicate localFlowBigStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - DataFlowType t, Configuration config, LocalCallContext callContext - ) { - localFlowStepPlus(node1, state1, node2, preservesValue, t, config, callContext) and - localFlowExit(node2, state1, config) and - state1 = state2 - or - additionalLocalFlowStepNodeCand2(node1, state1, node2, state2, config) and - state1 != state2 and - preservesValue = false and - t = node2.getDataFlowType() and - callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) - } -} - -private import LocalFlowBigStep - -private module Stage3Param implements MkStage::StageParam { - private module PrevStage = Stage2; - - class Ap = ApproxAccessPathFront; - - class ApNil = ApproxAccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } - - class ApHeadContent = ContentApprox; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - predicate projectToHeadContent = getContentApprox/1; - - class ApOption = ApproxAccessPathFrontOption; - - ApOption apNone() { result = TApproxAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } - - import BooleanCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApproxAccessPathFrontNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand2/6; - - predicate flowIntoCall = flowIntoCallNodeCand2/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getAHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage3 implements StageSig { - import MkStage::Stage -} - -private module Stage4Param implements MkStage::StageParam { - private module PrevStage = Stage3; - - class Ap = AccessPathFront; - - class ApNil = AccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathFrontOption; - - ApOption apNone() { result = TAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } - - import BooleanCallContext - - pragma[nomagic] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) and - exists(lcc) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clearSet(NodeEx node, ContentSet c, Configuration config) { - PrevStage::revFlow(node, config) and - clearsContentCached(node.asNode(), c) - } - - pragma[nomagic] - private predicate clearContent(NodeEx node, Content c, Configuration config) { - exists(ContentSet cs | - PrevStage::readStepCand(_, pragma[only_bind_into](c), _, pragma[only_bind_into](config)) and - c = cs.getAReadContent() and - clearSet(node, cs, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clear(NodeEx node, Ap ap, Configuration config) { - clearContent(node, ap.getHead().getContent(), config) - } - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - not clear(node, ap, config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage4 implements StageSig { - import MkStage::Stage -} - -/** - * Holds if `argApf` is recorded as the summary context for flow reaching `node` - * and remains relevant for the following pruning stage. - */ -private predicate flowCandSummaryCtx( - NodeEx node, FlowState state, AccessPathFront argApf, Configuration config -) { - exists(AccessPathFront apf | - Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf, config) and - Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf, - config) - ) -} - -/** - * Holds if a length 2 access path approximation with the head `tc` is expected - * to be expensive. - */ -private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { - exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf, config)) and - nodes = - strictcount(NodeEx n, FlowState state | - Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - or - flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - ) and - accessPathApproxCostLimits(apLimit, tupleLimit) and - apLimit < tails and - tupleLimit < (tails - 1) * nodes and - not tc.forceHighPrecision() - ) -} - -private newtype TAccessPathApprox = - TNil(DataFlowType t) or - TConsNil(TypedContent tc, DataFlowType t) { - Stage4::consCand(tc, TFrontNil(t), _) and - not expensiveLen2unfolding(tc, _) - } or - TConsCons(TypedContent tc1, TypedContent tc2, int len) { - Stage4::consCand(tc1, TFrontHead(tc2), _) and - len in [2 .. accessPathLimit()] and - not expensiveLen2unfolding(tc1, _) - } or - TCons1(TypedContent tc, int len) { - len in [1 .. accessPathLimit()] and - expensiveLen2unfolding(tc, _) - } - -/** - * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only - * the first two elements of the list and its length are tracked. If data flows - * from a source to a given node with a given `AccessPathApprox`, this indicates - * the sequence of dereference operations needed to get from the value in the node - * to the tracked object. The final type indicates the type of the tracked object. - */ -abstract private class AccessPathApprox extends TAccessPathApprox { - abstract string toString(); - - abstract TypedContent getHead(); - - abstract int len(); - - abstract DataFlowType getType(); - - abstract AccessPathFront getFront(); - - /** Gets the access path obtained by popping `head` from this path, if any. */ - abstract AccessPathApprox pop(TypedContent head); -} - -private class AccessPathApproxNil extends AccessPathApprox, TNil { - private DataFlowType t; - - AccessPathApproxNil() { this = TNil(t) } - - override string toString() { result = concat(": " + ppReprType(t)) } - - override TypedContent getHead() { none() } - - override int len() { result = 0 } - - override DataFlowType getType() { result = t } - - override AccessPathFront getFront() { result = TFrontNil(t) } - - override AccessPathApprox pop(TypedContent head) { none() } -} - -abstract private class AccessPathApproxCons extends AccessPathApprox { } - -private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { - private TypedContent tc; - private DataFlowType t; - - AccessPathApproxConsNil() { this = TConsNil(tc, t) } - - override string toString() { - // The `concat` becomes "" if `ppReprType` has no result. - result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) - } - - override TypedContent getHead() { result = tc } - - override int len() { result = 1 } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } -} - -private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { - private TypedContent tc1; - private TypedContent tc2; - private int len; - - AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - - override string toString() { - if len = 2 - then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" - else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc1 } - - override int len() { result = len } - - override DataFlowType getType() { result = tc1.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc1) } - - override AccessPathApprox pop(TypedContent head) { - head = tc1 and - ( - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - } -} - -private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { - private TypedContent tc; - private int len; - - AccessPathApproxCons1() { this = TCons1(tc, len) } - - override string toString() { - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc } - - override int len() { result = len } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { - head = tc and - ( - exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2), _) | - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - or - exists(DataFlowType t | - len = 1 and - Stage4::consCand(tc, TFrontNil(t), _) and - result = TNil(t) - ) - ) - } -} - -/** Gets the access path obtained by popping `tc` from `ap`, if any. */ -private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } - -/** Gets the access path obtained by pushing `tc` onto `ap`. */ -private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } - -private newtype TAccessPathApproxOption = - TAccessPathApproxNone() or - TAccessPathApproxSome(AccessPathApprox apa) - -private class AccessPathApproxOption extends TAccessPathApproxOption { - string toString() { - this = TAccessPathApproxNone() and result = "" - or - this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) - } -} - -private module Stage5Param implements MkStage::StageParam { - private module PrevStage = Stage4; - - class Ap = AccessPathApprox; - - class ApNil = AccessPathApproxNil; - - pragma[nomagic] - PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathApproxOption; - - ApOption apNone() { result = TAccessPathApproxNone() } - - ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } - - import Level1CallContext - import LocalCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, lcc) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { any() } - - // Type checking is not necessary here as it has already been done in stage 3. - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage5 = MkStage::Stage; - -bindingset[conf, result] -private Configuration unbindConf(Configuration conf) { - exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c)) -} - -pragma[nomagic] -private predicate nodeMayUseSummary0( - NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(AccessPathApprox apa0 | - Stage5::parameterMayFlowThrough(p, _, _) and - Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0, config) and - Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), - TAccessPathApproxSome(apa), apa0, config) - ) -} - -pragma[nomagic] -private predicate nodeMayUseSummary( - NodeEx n, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::parameterMayFlowThrough(p, apa, config) and - nodeMayUseSummary0(n, p, state, apa, config) - ) -} - -private newtype TSummaryCtx = - TSummaryCtxNone() or - TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { - exists(Configuration config | - Stage5::parameterMayFlowThrough(p, ap.getApprox(), config) and - Stage5::revFlow(p, state, _, config) - ) - } - -/** - * A context for generating flow summaries. This represents flow entry through - * a specific parameter with an access path of a specific shape. - * - * Summaries are only created for parameters that may flow through. - */ -abstract private class SummaryCtx extends TSummaryCtx { - abstract string toString(); -} - -/** A summary context from which no flow summary can be generated. */ -private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { - override string toString() { result = "" } -} - -/** A summary context from which a flow summary can be generated. */ -private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParamNodeEx p; - private FlowState s; - private AccessPath ap; - - SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } - - ParameterPosition getParameterPos() { p.isParameterOf(_, result) } - - ParamNodeEx getParamNode() { result = p } - - override string toString() { result = p + ": " + ap } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** - * Gets the number of length 2 access path approximations that correspond to `apa`. - */ -private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { - exists(TypedContent tc, int len | - tc = apa.getHead() and - len = apa.len() and - result = - strictcount(AccessPathFront apf | - Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), - config) - ) - ) -} - -private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = - strictcount(NodeEx n, FlowState state | - Stage5::revFlow(n, state, apa, config) or nodeMayUseSummary(n, state, apa, config) - ) -} - -/** - * Holds if a length 2 access path approximation matching `apa` is expected - * to be expensive. - */ -private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = count1to2unfold(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - apLimit < aps and - tupleLimit < (aps - 1) * nodes - ) -} - -private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { - exists(TypedContent head | - apa.pop(head) = result and - Stage5::consCand(head, result, config) - ) -} - -/** - * Holds with `unfold = false` if a precise head-tail representation of `apa` is - * expected to be expensive. Holds with `unfold = true` otherwise. - */ -private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { - if apa.getHead().forceHighPrecision() - then unfold = true - else - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = countPotentialAps(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true - ) -} - -/** - * Gets the number of `AccessPath`s that correspond to `apa`. - */ -pragma[assume_small_delta] -private int countAps(AccessPathApprox apa, Configuration config) { - evalUnfold(apa, false, config) and - result = 1 and - (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) - or - evalUnfold(apa, false, config) and - result = count1to2unfold(apa, config) and - not expensiveLen1to2unfolding(apa, config) - or - evalUnfold(apa, true, config) and - result = countPotentialAps(apa, config) -} - -/** - * Gets the number of `AccessPath`s that would correspond to `apa` assuming - * that it is expanded to a precise head-tail representation. - */ -language[monotonicAggregates] -pragma[assume_small_delta] -private int countPotentialAps(AccessPathApprox apa, Configuration config) { - apa instanceof AccessPathApproxNil and result = 1 - or - result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) -} - -private newtype TAccessPath = - TAccessPathNil(DataFlowType t) or - TAccessPathCons(TypedContent head, AccessPath tail) { - exists(AccessPathApproxCons apa | - not evalUnfold(apa, false, _) and - head = apa.getHead() and - tail.getApprox() = getATail(apa, _) - ) - } or - TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - not expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head1 = apa.getHead() and - head2 = getATail(apa, _).getHead() - ) - } or - TAccessPathCons1(TypedContent head, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head = apa.getHead() - ) - } - -private newtype TPathNode = - pragma[assume_small_delta] - TPathNodeMid( - NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config - ) { - // A PathNode is introduced by a source ... - Stage5::revFlow(node, state, config) and - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - or - // ... or a step from an existing PathNode to another node. - exists(PathNodeMid mid | - pathStep(mid, node, state, cc, sc, ap) and - pragma[only_bind_into](config) = mid.getConfiguration() and - Stage5::revFlow(node, state, ap.getApprox(), pragma[only_bind_into](config)) - ) - } or - TPathNodeSink(NodeEx node, FlowState state, Configuration config) { - exists(PathNodeMid sink | - sink.isAtSink() and - node = sink.getNodeEx() and - state = sink.getState() and - config = sink.getConfiguration() - ) - } or - TPathNodeSourceGroup(string sourceGroup, Configuration config) { - exists(PathNodeImpl source | - sourceGroup = source.getSourceGroup() and - config = source.getConfiguration() - ) - } or - TPathNodeSinkGroup(string sinkGroup, Configuration config) { - exists(PathNodeSink sink | - sinkGroup = sink.getSinkGroup() and - config = sink.getConfiguration() - ) - } - -/** - * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a - * source to a given node with a given `AccessPath`, this indicates the sequence - * of dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ -private class AccessPath extends TAccessPath { - /** Gets the head of this access path, if any. */ - abstract TypedContent getHead(); - - /** Gets the tail of this access path, if any. */ - abstract AccessPath getTail(); - - /** Gets the front of this access path. */ - abstract AccessPathFront getFront(); - - /** Gets the approximation of this access path. */ - abstract AccessPathApprox getApprox(); - - /** Gets the length of this access path. */ - abstract int length(); - - /** Gets a textual representation of this access path. */ - abstract string toString(); - - /** Gets the access path obtained by popping `tc` from this access path, if any. */ - final AccessPath pop(TypedContent tc) { - result = this.getTail() and - tc = this.getHead() - } - - /** Gets the access path obtained by pushing `tc` onto this access path. */ - final AccessPath push(TypedContent tc) { this = result.pop(tc) } -} - -private class AccessPathNil extends AccessPath, TAccessPathNil { - private DataFlowType t; - - AccessPathNil() { this = TAccessPathNil(t) } - - DataFlowType getType() { result = t } - - override TypedContent getHead() { none() } - - override AccessPath getTail() { none() } - - override AccessPathFrontNil getFront() { result = TFrontNil(t) } - - override AccessPathApproxNil getApprox() { result = TNil(t) } - - override int length() { result = 0 } - - override string toString() { result = concat(": " + ppReprType(t)) } -} - -private class AccessPathCons extends AccessPath, TAccessPathCons { - private TypedContent head; - private AccessPath tail; - - AccessPathCons() { this = TAccessPathCons(head, tail) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { result = tail } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - pragma[assume_small_delta] - override AccessPathApproxCons getApprox() { - result = TConsNil(head, tail.(AccessPathNil).getType()) - or - result = TConsCons(head, tail.getHead(), this.length()) - or - result = TCons1(head, this.length()) - } - - pragma[assume_small_delta] - override int length() { result = 1 + tail.length() } - - private string toStringImpl(boolean needsSuffix) { - exists(DataFlowType t | - tail = TAccessPathNil(t) and - needsSuffix = false and - result = head.toString() + "]" + concat(" : " + ppReprType(t)) - ) - or - result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) - or - exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | - result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true - or - result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false - ) - or - exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | - result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true - or - result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false - ) - } - - override string toString() { - result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" - or - result = "[" + this.toStringImpl(false) - } -} - -private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { - private TypedContent head1; - private TypedContent head2; - private int len; - - AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } - - override TypedContent getHead() { result = head1 } - - override AccessPath getTail() { - Stage5::consCand(head1, result.getApprox(), _) and - result.getHead() = head2 and - result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head1) } - - override AccessPathApproxCons getApprox() { - result = TConsCons(head1, head2, len) or - result = TCons1(head1, len) - } - - override int length() { result = len } - - override string toString() { - if len = 2 - then result = "[" + head1.toString() + ", " + head2.toString() + "]" - else - result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" - } -} - -private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { - private TypedContent head; - private int len; - - AccessPathCons1() { this = TAccessPathCons1(head, len) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { - Stage5::consCand(head, result.getApprox(), _) and result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - - override int length() { result = len } - - override string toString() { - if len = 1 - then result = "[" + head.toString() + "]" - else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" - } -} - -abstract private class PathNodeImpl extends TPathNode { - /** Gets the `FlowState` of this node. */ - abstract FlowState getState(); - - /** Gets the associated configuration. */ - abstract Configuration getConfiguration(); - - /** Holds if this node is a source. */ - abstract predicate isSource(); - - abstract PathNodeImpl getASuccessorImpl(); - - private PathNodeImpl getASuccessorIfHidden() { - this.isHidden() and - result = this.getASuccessorImpl() - } - - pragma[nomagic] - private PathNodeImpl getANonHiddenSuccessor0() { - result = this.getASuccessorIfHidden*() and - not result.isHidden() - } - - final PathNodeImpl getANonHiddenSuccessor() { - result = this.getASuccessorImpl().getANonHiddenSuccessor0() and - not this.isHidden() - } - - abstract NodeEx getNodeEx(); - - predicate isHidden() { - not this.getConfiguration().includeHiddenNodes() and - ( - hiddenNode(this.getNodeEx().asNode()) and - not this.isSource() and - not this instanceof PathNodeSink - or - this.getNodeEx() instanceof TNodeImplicitRead - ) - } - - string getSourceGroup() { - this.isSource() and - this.getConfiguration().sourceGrouping(this.getNodeEx().asNode(), result) + not singleConfiguration() and + getConfig(state1).isAdditionalFlowStep(node1, node2) and + state2 = state1 } - predicate isFlowSource() { - this.isSource() and not exists(this.getSourceGroup()) - or - this instanceof PathNodeSourceGroup + predicate allowImplicitRead(Node node, ContentSet c) { + any(Configuration config).allowImplicitRead(node, c) } - predicate isFlowSink() { - this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or - this instanceof PathNodeSinkGroup - } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } - private string ppAp() { - this instanceof PathNodeSink and result = "" - or - exists(string s | s = this.(PathNodeMid).getAp().toString() | - if s = "" then result = "" else result = " " + s - ) - } + FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } - private string ppCtx() { - this instanceof PathNodeSink and result = "" - or - result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + predicate sourceGrouping(Node source, string sourceGroup) { + any(Configuration config).sourceGrouping(source, sourceGroup) } - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + predicate sinkGrouping(Node sink, string sinkGroup) { + any(Configuration config).sinkGrouping(sink, sinkGroup) } -} - -/** Holds if `n` can reach a sink. */ -private predicate directReach(PathNodeImpl n) { - n instanceof PathNodeSink or - n instanceof PathNodeSinkGroup or - directReach(n.getANonHiddenSuccessor()) -} - -/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ -private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } -/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { - n1.getANonHiddenSuccessor() = n2 and directReach(n2) + predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() } } -private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) +private import Impl as I +import I /** * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. */ -class PathNode instanceof PathNodeImpl { - PathNode() { reach(this) } - +class PathNode instanceof I::PathNode { /** Gets a textual representation of this element. */ final string toString() { result = super.toString() } @@ -3406,1548 +358,39 @@ class PathNode instanceof PathNodeImpl { } /** Gets the underlying `Node`. */ - final Node getNode() { super.getNodeEx().projectToNode() = result } + final Node getNode() { result = super.getNode() } /** Gets the `FlowState` of this node. */ - final FlowState getState() { result = super.getState() } + final FlowState getState() { result = getState(super.getState()) } /** Gets the associated configuration. */ - final Configuration getConfiguration() { result = super.getConfiguration() } + final Configuration getConfiguration() { result = getConfig(super.getState()) } /** Gets a successor of this node, if any. */ - final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } + final PathNode getASuccessor() { result = super.getASuccessor() } /** Holds if this node is a source. */ final predicate isSource() { super.isSource() } /** Holds if this node is a grouping of source nodes. */ - final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group, _) } + final predicate isSourceGroup(string group) { super.isSourceGroup(group) } /** Holds if this node is a grouping of sink nodes. */ - final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group, _) } + final predicate isSinkGroup(string group) { super.isSinkGroup(group) } } -/** - * Provides the query predicates needed to include a graph in a path-problem query. - */ -module PathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } - - /** Holds if `n` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode n, string key, string val) { - key = "semmle.label" and val = n.toString() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { - Subpaths::subpaths(arg, par, ret, out) - } -} - -/** - * An intermediate flow graph node. This is a triple consisting of a `Node`, - * a `CallContext`, and a `Configuration`. - */ -private class PathNodeMid extends PathNodeImpl, TPathNodeMid { - NodeEx node; - FlowState state; - CallContext cc; - SummaryCtx sc; - AccessPath ap; - Configuration config; - - PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - SummaryCtx getSummaryCtx() { result = sc } - - AccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - private PathNodeMid getSuccMid() { - pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx(), result.getAp()) and - result.getConfiguration() = unbindConf(this.getConfiguration()) - } - - override PathNodeImpl getASuccessorImpl() { - // an intermediate step to another intermediate node - result = this.getSuccMid() - or - // a final step to a sink - result = this.getSuccMid().projectToSink() - } - - override predicate isSource() { - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - } - - predicate isAtSink() { - sinkNode(node, state, config) and - ap instanceof AccessPathNil and - if hasSinkCallCtx(config) - then - // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` - // is exactly what we need to check. This also implies - // `sc instanceof SummaryCtxNone`. - // For `FeatureEqualSourceSinkCallContext` the initial call context was - // set to `CallContextSomeCall` and jumps are disallowed, so - // `cc instanceof CallContextNoCall` never holds. On the other hand, - // in this case there's never any need to enter a call except to identify - // a summary, so the condition in `pathIntoCallable` enforces this, which - // means that `sc instanceof SummaryCtxNone` holds if and only if we are - // in the call context of the source. - sc instanceof SummaryCtxNone or - cc instanceof CallContextNoCall - else any() - } - - PathNodeSink projectToSink() { - this.isAtSink() and - result.getNodeEx() = node and - result.getState() = state and - result.getConfiguration() = unbindConf(config) - } -} - -/** - * A flow graph node corresponding to a sink. This is disjoint from the - * intermediate nodes in order to uniquely correspond to a given sink by - * excluding the `CallContext`. - */ -private class PathNodeSink extends PathNodeImpl, TPathNodeSink { - NodeEx node; - FlowState state; - Configuration config; - - PathNodeSink() { this = TPathNodeSink(node, state, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result = TPathNodeSinkGroup(this.getSinkGroup(), config) - } - - override predicate isSource() { sourceNode(node, state, config) } - - string getSinkGroup() { config.sinkGrouping(node.asNode(), result) } -} - -private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { - string sourceGroup; - Configuration config; - - PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result.getSourceGroup() = sourceGroup and - result.getConfiguration() = config - } - - override predicate isSource() { none() } - - override string toString() { result = sourceGroup } - - override 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 - } -} - -private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { - string sinkGroup; - Configuration config; - - PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { none() } - - override predicate isSource() { none() } - - override string toString() { result = sinkGroup } - - override 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 - } -} - -private predicate pathNode( - PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, - Configuration conf, LocalCallContext localCC -) { - midnode = mid.getNodeEx() and - state = mid.getState() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - midnode.getEnclosingCallable()) and - ap = mid.getAp() -} - -/** - * Holds if data may flow from `mid` to `node`. The last step in or out of - * a callable is recorded by `cc`. - */ -pragma[assume_small_delta] -pragma[nomagic] -private predicate pathStep( - PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap -) { - exists(NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC | - pathNode(mid, midnode, state0, cc, sc, ap, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, true, _, conf, localCC) - ) - or - exists( - AccessPath ap0, NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC - | - pathNode(mid, midnode, state0, cc, sc, ap0, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), conf, - localCC) and - ap0 instanceof AccessPathNil - ) - or - jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - ap = mid.getAp() - or - additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, mid.getConfiguration()) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - pathIntoCallable(mid, node, state, _, cc, sc, _, _) and ap = mid.getAp() - or - pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone - or - pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() -} - -pragma[nomagic] -private predicate pathReadStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - tc = ap0.getHead() and - Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -pragma[nomagic] -private predicate pathStoreStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -private predicate pathOutOfCallable0( - PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa, - Configuration config -) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - apa = mid.getAp().getApprox() and - config = mid.getConfiguration() -} - -pragma[nomagic] -private predicate pathOutOfCallable1( - PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPathApprox apa, Configuration config -) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - pathOutOfCallable0(mid, pos, state, innercc, apa, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() +private predicate hasFlow(Node source, Node sink, Configuration config) { + exists(PathNode source0, PathNode sink0 | + hasFlowPath(source0, sink0, config) and + source0.getNode() = source and + sink0.getNode() = sink ) } -pragma[noinline] -private NodeEx getAnOutNodeFlow( - ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config -) { - result.asNode() = kind.getAnOutNode(call) and - Stage5::revFlow(result, _, apa, config) +private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) { + hasFlowPath(source, sink) and source.getConfiguration() = config } -/** - * Holds if data may flow from `mid` to `out`. The last step of this path - * is a return from a callable and is recorded by `cc`, if needed. - */ -pragma[noinline] -private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { - exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | - pathOutOfCallable1(mid, call, kind, state, cc, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} +private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) } -/** - * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. - */ -pragma[noinline] -private predicate pathIntoArg( - PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(ArgNodeEx arg, ArgumentPosition apos | - pathNode(mid, arg, state, cc, _, ap, config, _) and - arg.asNode().(ArgNode).argumentOf(call, apos) and - apa = ap.getApprox() and - parameterMatch(ppos, apos) - ) -} - -pragma[nomagic] -private predicate parameterCand( - DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::revFlow(p, _, apa, config) and - p.isParameterOf(callable, pos) - ) -} - -pragma[nomagic] -private predicate pathIntoCallable0( - PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, AccessPath ap, Configuration config -) { - exists(AccessPathApprox apa | - pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - callable = resolveCall(call, outercc) and - parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa), - pragma[only_bind_into](config)) - ) -} - -/** - * Holds if data may flow from `mid` to `p` through `call`. The contexts - * before and after entering the callable are `outercc` and `innercc`, - * respectively. - */ -pragma[nomagic] -private predicate pathIntoCallable( - PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, - SummaryCtx sc, DataFlowCall call, Configuration config -) { - exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | - pathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - ( - sc = TSummaryCtxSome(p, state, ap) - or - not exists(TSummaryCtxSome(p, state, ap)) and - sc = TSummaryCtxNone() and - // When the call contexts of source and sink needs to match then there's - // never any reason to enter a callable except to find a summary. See also - // the comment in `PathNodeMid::isAtSink`. - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) -} - -/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ -pragma[nomagic] -private predicate paramFlowsThrough( - ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, - AccessPathApprox apa, Configuration config -) { - exists(RetNodeEx ret | - pathNode(_, ret, state, cc, sc, ap, config, _) and - kind = ret.getKind() and - apa = ap.getApprox() and - parameterFlowThroughAllowed(sc.getParamNode(), kind) - ) -} - -pragma[nomagic] -private predicate pathThroughCallable0( - DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(CallContext innercc, SummaryCtx sc | - pathIntoCallable(mid, _, _, cc, innercc, sc, call, config) and - paramFlowsThrough(kind, state, innercc, sc, ap, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` through a callable to the node `out`. - * The context `cc` is restored to its value prior to entering the callable. - */ -pragma[noinline] -private predicate pathThroughCallable( - PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap -) { - exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config | - pathThroughCallable0(call, mid, kind, state, cc, ap, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -private module Subpaths { - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths01( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - exists(Configuration config | - pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and - pathIntoCallable(arg, par, _, _, innercc, sc, _, config) and - paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, - pragma[only_bind_into](apout), _, unbindConf(config)) and - not arg.isHidden() - ) - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `sout`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths02( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and - out.asNode() = kind.getAnOutNode(_) - } - - pragma[nomagic] - private Configuration getPathNodeConf(PathNodeImpl n) { result = n.getConfiguration() } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple. - */ - pragma[nomagic] - private predicate subpaths03( - PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, AccessPath apout - ) { - exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | - subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and - pathNode(ret, retnode, sout, innercc, sc, apout, unbindConf(getPathNodeConf(arg)), _) and - kind = retnode.getKind() - ) - } - - private PathNodeImpl localStepToHidden(PathNodeImpl n) { - n.getASuccessorImpl() = result and - result.isHidden() and - exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | - localFlowBigStep(n1, _, n2, _, _, _, _, _) or - store(n1, _, n2, _, _) or - readSet(n1, _, n2, _) - ) - } - - pragma[nomagic] - private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { - succ = pred.getANonHiddenSuccessor() and - succNode = succ.getNodeEx() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { - exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | - pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and - subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and - hasSuccessor(pragma[only_bind_into](arg), par, p) and - not ret.isHidden() and - pathNode(out0, o, sout, _, _, apout, _, _) - | - out = out0 or out = out0.projectToSink() - ) - } - - /** - * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. - */ - predicate retReach(PathNodeImpl n) { - exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) - or - exists(PathNodeImpl mid | - retReach(mid) and - n.getANonHiddenSuccessor() = mid and - not subpaths(_, mid, _, _) - ) - } -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -private predicate hasFlowPath( - PathNodeImpl flowsource, PathNodeImpl flowsink, Configuration configuration -) { - flowsource.isFlowSource() and - flowsource.getConfiguration() = configuration and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.isFlowSink() -} - -private predicate flowsTo( - PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink, - Configuration configuration -) { - flowsource.isSource() and - flowsource.getConfiguration() = configuration and - flowsource.getNodeEx().asNode() = source and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.getNodeEx().asNode() = sink -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -predicate flowsTo(Node source, Node sink, Configuration configuration) { - flowsTo(_, _, source, sink, configuration) -} - -private predicate finalStats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples -) { - fwd = true and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and - tuples = count(PathNodeImpl pn) - or - fwd = false and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and - tuples = count(PathNode pn) -} - -/** - * INTERNAL: Only for debugging. - * - * Calculates per-stage metrics for data flow. - */ -predicate stageStats( - int n, string stage, int nodes, int fields, int conscand, int states, int tuples, - Configuration config -) { - stage = "1 Fwd" and - n = 10 and - Stage1::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "1 Rev" and - n = 15 and - Stage1::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Fwd" and - n = 20 and - Stage2::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Rev" and - n = 25 and - Stage2::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Fwd" and - n = 30 and - Stage3::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Rev" and - n = 35 and - Stage3::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Fwd" and - n = 40 and - Stage4::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Rev" and - n = 45 and - Stage4::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Fwd" and - n = 50 and - Stage5::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Rev" and - n = 55 and - Stage5::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) - or - stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) -} - -private module FlowExploration { - private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - jumpStep(node1, node2, config) - or - additionalJumpStep(node1, node2, config) - or - additionalJumpStateStep(node1, _, node2, _, config) - or - // flow into callable - viableParamArgEx(_, node2, node1) - or - // flow out of a callable - viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) - | - c1 = node1.getEnclosingCallable() and - c2 = node2.getEnclosingCallable() and - c1 != c2 - ) - } - - private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSource(n) or config.isSource(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSrc(mid, config) and callableStep(mid, c, config) - ) - } - - private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSink(n) or config.isSink(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSink(mid, config) and callableStep(c, mid, config) - ) - } - - private newtype TCallableExt = - TCallable(DataFlowCallable c, Configuration config) { - interestingCallableSrc(c, config) or - interestingCallableSink(c, config) - } or - TCallableSrc() or - TCallableSink() - - private predicate callableExtSrc(TCallableSrc src) { any() } - - private predicate callableExtSink(TCallableSink sink) { any() } - - private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { - exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | - callableStep(c1, c2, config) and - ce1 = TCallable(c1, pragma[only_bind_into](config)) and - ce2 = TCallable(c2, pragma[only_bind_into](config)) - ) - or - exists(Node n, Configuration config | - ce1 = TCallableSrc() and - (config.isSource(n) or config.isSource(n, _)) and - ce2 = TCallable(getNodeEnclosingCallable(n), config) - ) - or - exists(Node n, Configuration config | - ce2 = TCallableSink() and - (config.isSink(n) or config.isSink(n, _)) and - ce1 = TCallable(getNodeEnclosingCallable(n), config) - ) - } - - private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { - callableExtStepFwd(ce2, ce1) - } - - private int distSrcExt(TCallableExt c) = - shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) - - private int distSinkExt(TCallableExt c) = - shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) - - private int distSrc(DataFlowCallable c, Configuration config) { - result = distSrcExt(TCallable(c, config)) - 1 - } - - private int distSink(DataFlowCallable c, Configuration config) { - result = distSinkExt(TCallable(c, config)) - 1 - } - - private newtype TPartialAccessPath = - TPartialNil(DataFlowType t) or - TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first - * element of the list and its length are tracked. If data flows from a source to - * a given node with a given `AccessPath`, this indicates the sequence of - * dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ - private class PartialAccessPath extends TPartialAccessPath { - abstract string toString(); - - TypedContent getHead() { this = TPartialCons(result, _) } - - int len() { - this = TPartialNil(_) and result = 0 - or - this = TPartialCons(_, result) - } - - DataFlowType getType() { - this = TPartialNil(result) - or - exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) - } - } - - private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { - override string toString() { - exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) - } - } - - private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { - override string toString() { - exists(TypedContent tc, int len | this = TPartialCons(tc, len) | - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private newtype TRevPartialAccessPath = - TRevPartialNil() or - TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `Content`s, but only the first - * element of the list and its length are tracked. - */ - private class RevPartialAccessPath extends TRevPartialAccessPath { - abstract string toString(); - - Content getHead() { this = TRevPartialCons(result, _) } - - int len() { - this = TRevPartialNil() and result = 0 - or - this = TRevPartialCons(_, result) - } - } - - private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { - override string toString() { result = "" } - } - - private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { - override string toString() { - exists(Content c, int len | this = TRevPartialCons(c, len) | - if len = 1 - then result = "[" + c.toString() + "]" - else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private predicate relevantState(FlowState state) { - sourceNode(_, state, _) or - sinkNode(_, state, _) or - additionalLocalStateStep(_, state, _, _, _) or - additionalLocalStateStep(_, _, _, state, _) or - additionalJumpStateStep(_, state, _, _, _) or - additionalJumpStateStep(_, _, _, state, _) - } - - private newtype TSummaryCtx1 = - TSummaryCtx1None() or - TSummaryCtx1Param(ParamNodeEx p) - - private newtype TSummaryCtx2 = - TSummaryCtx2None() or - TSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TSummaryCtx3 = - TSummaryCtx3None() or - TSummaryCtx3Some(PartialAccessPath ap) - - private newtype TRevSummaryCtx1 = - TRevSummaryCtx1None() or - TRevSummaryCtx1Some(ReturnPosition pos) - - private newtype TRevSummaryCtx2 = - TRevSummaryCtx2None() or - TRevSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TRevSummaryCtx3 = - TRevSummaryCtx3None() or - TRevSummaryCtx3Some(RevPartialAccessPath ap) - - private newtype TPartialPathNode = - TPartialPathNodeFwd( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = TPartialNil(node.getDataFlowType()) and - exists(config.explorationLimit()) - or - partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap, config) and - distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() - } or - TPartialPathNodeRev( - NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, - RevPartialAccessPath ap, Configuration config - ) { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() and - exists(config.explorationLimit()) - or - revPartialPathStep(_, node, state, sc1, sc2, sc3, ap, config) and - not clearsContentEx(node, ap.getHead()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead()) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() - } - - pragma[nomagic] - private predicate partialPathNodeMk0( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap, config) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - not clearsContentEx(node, ap.getHead().getContent()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead().getContent()) - ) and - if node.asNode() instanceof CastingNode - then compatibleTypes(node.getDataFlowType(), ap.getType()) - else any() - } - - /** - * A `Node` augmented with a call context, an access path, and a configuration. - */ - class PartialPathNode extends TPartialPathNode { - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() - } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - /** Gets the underlying `Node`. */ - final Node getNode() { this.getNodeEx().projectToNode() = result } - - FlowState getState() { none() } - - private NodeEx getNodeEx() { - result = this.(PartialPathNodeFwd).getNodeEx() or - result = this.(PartialPathNodeRev).getNodeEx() - } - - /** Gets the associated configuration. */ - Configuration getConfiguration() { none() } - - /** Gets a successor of this node, if any. */ - PartialPathNode getASuccessor() { none() } - - /** - * Gets the approximate distance to the nearest source measured in number - * of interprocedural steps. - */ - int getSourceDistance() { - result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - /** - * Gets the approximate distance to the nearest sink measured in number - * of interprocedural steps. - */ - int getSinkDistance() { - result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - private string ppAp() { - exists(string s | - s = this.(PartialPathNodeFwd).getAp().toString() or - s = this.(PartialPathNodeRev).getAp().toString() - | - if s = "" then result = "" else result = " " + s - ) - } - - private string ppCtx() { - result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" - } - - /** Holds if this is a source in a forward-flow path. */ - predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } - - /** Holds if this is a sink in a reverse-flow path. */ - predicate isRevSink() { this.(PartialPathNodeRev).isSink() } - } - - /** - * Provides the query predicates needed to include a graph in a path-problem query. - */ - module PartialPathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } - } - - private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { - NodeEx node; - FlowState state; - CallContext cc; - TSummaryCtx1 sc1; - TSummaryCtx2 sc2; - TSummaryCtx3 sc3; - PartialAccessPath ap; - Configuration config; - - PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - TSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TSummaryCtx3 getSummaryCtx3() { result = sc3 } - - PartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeFwd getASuccessor() { - partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp(), - result.getConfiguration()) - } - - predicate isSource() { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap instanceof TPartialNil - } - } - - private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { - NodeEx node; - FlowState state; - TRevSummaryCtx1 sc1; - TRevSummaryCtx2 sc2; - TRevSummaryCtx3 sc3; - RevPartialAccessPath ap; - Configuration config; - - PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } - - RevPartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeRev getASuccessor() { - revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), - this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp(), this.getConfiguration()) - } - - predicate isSink() { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() - } - } - - private predicate partialPathStep( - PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, - TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and - ( - localFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - ) - or - jumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - partialPathStoreStep(mid, _, _, node, ap) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(PartialAccessPath ap0, TypedContent tc | - partialPathReadStep(mid, ap0, tc, node, cc, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsFwd(ap, tc, ap0, config) - ) - or - partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap, config) - or - partialPathOutOfCallable(mid, node, state, cc, ap, config) and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() - or - partialPathThroughCallable(mid, node, state, cc, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - bindingset[result, i] - private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } - - pragma[inline] - private predicate partialPathStoreStep( - PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, - PartialAccessPath ap2 - ) { - exists(NodeEx midNode, DataFlowType contentType | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - store(midNode, tc, node, contentType, mid.getConfiguration()) and - ap2.getHead() = tc and - ap2.len() = unbindInt(ap1.len() + 1) and - compatibleTypes(ap1.getType(), contentType) - ) - } - - pragma[nomagic] - private predicate apConsFwd( - PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeFwd mid | - partialPathStoreStep(mid, ap1, tc, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate partialPathReadStep( - PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc, - Configuration config - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and - ap.getHead() = tc and - pragma[only_bind_into](config) = mid.getConfiguration() and - cc = mid.getCallContext() - ) - } - - private predicate partialPathOutOfCallable0( - PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, - PartialAccessPath ap, Configuration config - ) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - ap = mid.getAp() and - config = mid.getConfiguration() - } - - pragma[nomagic] - private predicate partialPathOutOfCallable1( - PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - partialPathOutOfCallable0(mid, pos, state, innercc, ap, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) - } - - private predicate partialPathOutOfCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(ReturnKindExt kind, DataFlowCall call | - partialPathOutOfCallable1(mid, call, kind, state, cc, ap, config) - | - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[noinline] - private predicate partialPathIntoArg( - PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ArgNode arg, ArgumentPosition apos | - arg = mid.getNodeEx().asNode() and - state = mid.getState() and - cc = mid.getCallContext() and - arg.argumentOf(call, apos) and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate partialPathIntoCallable0( - PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - partialPathIntoArg(mid, pos, state, outercc, call, ap, config) and - callable = resolveCall(call, outercc) - } - - private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, - CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ParameterPosition pos, DataFlowCallable callable | - partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - sc1 = TSummaryCtx1Param(p) and - sc2 = TSummaryCtx2Some(state) and - sc3 = TSummaryCtx3Some(ap) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) - } - - pragma[nomagic] - private predicate paramFlowsThroughInPartialPath( - ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeFwd mid, RetNodeEx ret | - mid.getNodeEx() = ret and - kind = ret.getKind() and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() and - ap = mid.getAp() - ) - } - - pragma[noinline] - private predicate partialPathThroughCallable0( - DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | - partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _, config) and - paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap, config) - ) - } - - private predicate partialPathThroughCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ReturnKindExt kind | - partialPathThroughCallable0(call, mid, kind, state, cc, ap, config) and - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[nomagic] - private predicate revPartialPathStep( - PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, - TRevSummaryCtx3 sc3, RevPartialAccessPath ap, Configuration config - ) { - localFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - jumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - revPartialPathReadStep(mid, _, _, node, ap) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(RevPartialAccessPath ap0, Content c | - revPartialPathStoreStep(mid, ap0, c, node, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsRev(ap, c, ap0, config) - ) - or - exists(ParamNodeEx p | - mid.getNodeEx() = p and - viableParamArgEx(_, p, node) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - or - exists(ReturnPosition pos | - revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap, config) and - pos = getReturnPosition(node.asNode()) - ) - or - revPartialPathThroughCallable(mid, node, state, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - pragma[inline] - private predicate revPartialPathReadStep( - PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, - RevPartialAccessPath ap2 - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - read(node, c, midNode, mid.getConfiguration()) and - ap2.getHead() = c and - ap2.len() = unbindInt(ap1.len() + 1) - ) - } - - pragma[nomagic] - private predicate apConsRev( - RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeRev mid | - revPartialPathReadStep(mid, ap1, c, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathStoreStep( - PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config - ) { - exists(NodeEx midNode, TypedContent tc | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - store(node, tc, midNode, _, config) and - ap.getHead() = c and - config = mid.getConfiguration() and - tc.getContent() = c - ) - } - - pragma[nomagic] - private predicate revPartialPathIntoReturn( - PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, - TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap, - Configuration config - ) { - exists(NodeEx out | - mid.getNodeEx() = out and - mid.getState() = state and - viableReturnPosOutEx(call, pos, out) and - sc1 = TRevSummaryCtx1Some(pos) and - sc2 = TRevSummaryCtx2Some(state) and - sc3 = TRevSummaryCtx3Some(ap) and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathFlowsThrough( - ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, - TRevSummaryCtx3Some sc3, RevPartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | - mid.getNodeEx() = p and - mid.getState() = state and - p.getPosition() = ppos and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable0( - DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, - RevPartialAccessPath ap, Configuration config - ) { - exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | - revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _, config) and - revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap, config) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ArgumentPosition pos | - revPartialPathThroughCallable0(call, mid, pos, state, ap, config) and - node.asNode().(ArgNode).argumentOf(call, pos) - ) - } -} - -import FlowExploration - -private predicate partialFlow( - PartialPathNode source, PartialPathNode node, Configuration configuration -) { - source.getConfiguration() = configuration and - source.isFwdSource() and - node = source.getASuccessor+() -} - -private predicate revPartialFlow( - PartialPathNode node, PartialPathNode sink, Configuration configuration -) { - sink.getConfiguration() = configuration and - sink.isRevSink() and - node.getASuccessor+() = sink -} +predicate flowsTo = hasFlow/3; diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForSerializability.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForSerializability.qll index 1b969756b09..e6bdc74cceb 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForSerializability.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForSerializability.qll @@ -1,16 +1,16 @@ /** - * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from - * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed - * through the `Configuration` class. This file exists in several identical - * copies, allowing queries to use multiple `Configuration` classes that depend - * on each other without introducing mutual recursion among those configurations. + * DEPRECATED: Use `Make` and `MakeWithState` instead. + * + * Provides a `Configuration` class backwards-compatible interface to the data + * flow library. */ private import DataFlowImplCommon private import DataFlowImplSpecific::Private import DataFlowImplSpecific::Public +private import DataFlowImpl import DataFlowImplCommonPublic +import FlowStateString /** * A configuration of interprocedural data flow analysis. This defines @@ -144,6 +144,10 @@ abstract class Configuration extends string { * - `FeatureEqualSourceSinkCallContext`: * Implies both of the above and additionally ensures that the entire flow * path preserves the call context. + * + * These features are generally not relevant for typical end-to-end data flow + * queries, but should only be used for constructing paths that need to + * somehow be pluggable in another path context. */ FlowFeature getAFeature() { none() } @@ -156,7 +160,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from `source` to `sink` for this configuration. */ - predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } + predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) } /** * Holds if data may flow from `source` to `sink` for this configuration. @@ -169,9 +173,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from some source to `sink` for this configuration. */ - predicate hasFlowTo(Node sink) { - sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode() - } + predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) } /** * Holds if data may flow from some source to `sink` for this configuration. @@ -179,10 +181,12 @@ abstract class Configuration extends string { predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } /** + * DEPRECATED: Use `FlowExploration` instead. + * * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` * measured in approximate number of interprocedural steps. */ - int explorationLimit() { none() } + deprecated int explorationLimit() { none() } /** * Holds if hidden nodes should be included in the data flow graph. @@ -191,49 +195,6 @@ abstract class Configuration extends string { * is not visualized (for example in a `path-problem` query). */ predicate includeHiddenNodes() { none() } - - /** - * Holds if there is a partial data flow path from `source` to `node`. The - * approximate distance between `node` and the closest source is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards sink definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sources is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - */ - final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { - partialFlow(source, node, this) and - dist = node.getSourceDistance() - } - - /** - * Holds if there is a partial data flow path from `node` to `sink`. The - * approximate distance between `node` and the closest sink is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards source definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sinks is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - * - * Note that reverse flow has slightly lower precision than the corresponding - * forward flow, as reverse flow disregards type pruning among other features. - */ - final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { - revPartialFlow(node, sink, this) and - dist = node.getSinkDistance() - } } /** @@ -263,90 +224,6 @@ abstract private class ConfigurationRecursionPrevention extends Configuration { } } -private newtype TNodeEx = - TNodeNormal(Node n) or - TNodeImplicitRead(Node n, boolean hasRead) { - any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true] - } - -private class NodeEx extends TNodeEx { - string toString() { - result = this.asNode().toString() - or - exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") - } - - Node asNode() { this = TNodeNormal(result) } - - predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } - - Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } - - pragma[nomagic] - private DataFlowCallable getEnclosingCallable0() { - nodeEnclosingCallable(this.projectToNode(), result) - } - - pragma[inline] - DataFlowCallable getEnclosingCallable() { - pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) - } - - pragma[nomagic] - private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } - - pragma[inline] - DataFlowType getDataFlowType() { - pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) - } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -private class ArgNodeEx extends NodeEx { - ArgNodeEx() { this.asNode() instanceof ArgNode } -} - -private class ParamNodeEx extends NodeEx { - ParamNodeEx() { this.asNode() instanceof ParamNode } - - predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { - this.asNode().(ParamNode).isParameterOf(c, pos) - } - - ParameterPosition getPosition() { this.isParameterOf(_, result) } -} - -private class RetNodeEx extends NodeEx { - RetNodeEx() { this.asNode() instanceof ReturnNodeExt } - - ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } - - ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } -} - -private predicate inBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierIn(n) - | - config.isSource(n) or config.isSource(n, _) - ) -} - -private predicate outBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierOut(n) - | - config.isSink(n) or config.isSink(n, _) - ) -} - /** A bridge class to access the deprecated `isBarrierGuard`. */ private class BarrierGuardGuardedNodeBridge extends Unit { abstract predicate guardedNode(Node n, Configuration config); @@ -370,3019 +247,94 @@ private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { } } -pragma[nomagic] -private predicate fullBarrier(NodeEx node, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n) +private FlowState relevantState(Configuration config) { + config.isSource(_, result) or + config.isSink(_, result) or + config.isBarrier(_, result) or + config.isAdditionalFlowStep(_, result, _, _) or + config.isAdditionalFlowStep(_, _, _, result) +} + +private newtype TConfigState = + TMkConfigState(Configuration config, FlowState state) { + state = relevantState(config) or state instanceof FlowStateEmpty + } + +private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) } + +private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) } + +private predicate singleConfiguration() { 1 = strictcount(Configuration c) } + +private module Config implements FullStateConfigSig { + class FlowState = TConfigState; + + predicate isSource(Node source, FlowState state) { + getConfig(state).isSource(source, getState(state)) or - config.isBarrierIn(n) and - not config.isSource(n) and - not config.isSource(n, _) + getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty + } + + predicate isSink(Node sink, FlowState state) { + getConfig(state).isSink(sink, getState(state)) or - config.isBarrierOut(n) and - not config.isSink(n) and - not config.isSink(n, _) + getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty + } + + predicate isBarrier(Node node) { none() } + + predicate isBarrier(Node node, FlowState state) { + getConfig(state).isBarrier(node, getState(state)) or + getConfig(state).isBarrier(node) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or + any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state)) + } + + predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) } + + predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) } + + predicate isAdditionalFlowStep(Node node1, Node node2) { + singleConfiguration() and + any(Configuration config).isAdditionalFlowStep(node1, node2) + } + + predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and + getConfig(state2) = getConfig(state1) or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config) - ) -} - -pragma[nomagic] -private predicate stateBarrier(NodeEx node, FlowState state, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n, state) - or - any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config) - ) -} - -pragma[nomagic] -private predicate sourceNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSource(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSource(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -pragma[nomagic] -private predicate sinkNode(NodeEx node, FlowState state, Configuration config) { - ( - config.isSink(node.asNode()) and state instanceof FlowStateEmpty - or - config.isSink(node.asNode(), state) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) -} - -/** Provides the relevant barriers for a step from `node1` to `node2`. */ -pragma[inline] -private predicate stepFilter(NodeEx node1, NodeEx node2, Configuration config) { - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) -} - -/** - * Holds if data can flow in one local step from `node1` to `node2`. - */ -private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.asNode() = n and - node2.isImplicitReadNode(n, false) and - not fullBarrier(node1, config) - ) -} - -/** - * Holds if the additional step from `node1` to `node2` does not jump between callables. - */ -private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.isImplicitReadNode(n, true) and - node2.asNode() = n and - not fullBarrier(node2, config) - ) -} - -private predicate additionalLocalStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) - ) -} - -/** - * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. - */ -private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if the additional step from `node1` to `node2` jumps between callables. - */ -private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate additionalJumpStateStep( - NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config -) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - stepFilter(node1, node2, config) and - not stateBarrier(node1, s1, config) and - not stateBarrier(node2, s2, config) and - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) -} - -pragma[nomagic] -private predicate readSet(NodeEx node1, ContentSet c, NodeEx node2, Configuration config) { - readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and - stepFilter(node1, node2, config) - or - exists(Node n | - node2.isImplicitReadNode(n, true) and - node1.isImplicitReadNode(n, _) and - config.allowImplicitRead(n, c) - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(ContentSet cs | - readSet(node1, cs, node2, config) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate clearsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - clearsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -// inline to reduce fan-out via `getAReadContent` -bindingset[c] -private predicate expectsContentEx(NodeEx n, Content c) { - exists(ContentSet cs | - expectsContentCached(n.asNode(), cs) and - pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent() - ) -} - -pragma[nomagic] -private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) } - -pragma[nomagic] -private predicate hasReadStep(Content c, Configuration config) { read(_, c, _, config) } - -pragma[nomagic] -private predicate store( - NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config -) { - store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()), - contentType) and - hasReadStep(tc.getContent(), config) and - stepFilter(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { - viableReturnPosOut(call, pos, out.asNode()) -} - -pragma[nomagic] -private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { - viableParamArg(call, p.asNode(), arg.asNode()) -} - -/** - * Holds if field flow should be used for the given configuration. - */ -private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } - -private predicate hasSourceCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSourceCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -private predicate hasSinkCallCtx(Configuration config) { - exists(FlowFeature feature | feature = config.getAFeature() | - feature instanceof FeatureHasSinkCallContext or - feature instanceof FeatureEqualSourceSinkCallContext - ) -} - -/** - * Holds if flow from `p` to a return node of kind `kind` is allowed. - * - * We don't expect a parameter to return stored in itself, unless - * explicitly allowed - */ -bindingset[p, kind] -private predicate parameterFlowThroughAllowed(ParamNodeEx p, ReturnKindExt kind) { - exists(ParameterPosition pos | p.isParameterOf(_, pos) | - not kind.(ParamUpdateReturnKind).getPosition() = pos - or - allowParameterReturnInSelfCached(p.asNode()) - ) -} - -private module Stage1 implements StageSig { - class Ap extends int { - // workaround for bad functionality-induced joins (happens when using `Unit`) - pragma[nomagic] - Ap() { this in [0 .. 1] and this < 1 } - } - - private class Cc = boolean; - - /* Begin: Stage 1 logic. */ - /** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `cc` records whether the node is reached through an - * argument in a call. - */ - private predicate fwdFlow(NodeEx node, Cc cc, Configuration config) { - sourceNode(node, _, config) and - if hasSourceCallCtx(config) then cc = true else cc = false - or - exists(NodeEx mid | fwdFlow(mid, cc, config) | - localFlowStep(mid, node, config) or - additionalLocalFlowStep(mid, node, config) or - additionalLocalStateStep(mid, _, node, _, config) - ) - or - exists(NodeEx mid | fwdFlow(mid, _, config) and cc = false | - jumpStep(mid, node, config) or - additionalJumpStep(mid, node, config) or - additionalJumpStateStep(mid, _, node, _, config) - ) - or - // store - exists(NodeEx mid | - useFieldFlow(config) and - fwdFlow(mid, cc, config) and - store(mid, _, node, _, config) - ) - or - // read - exists(ContentSet c | - fwdFlowReadSet(c, node, cc, config) and - fwdFlowConsCandSet(c, _, config) - ) - or - // flow into a callable - fwdFlowIn(_, _, _, node, config) and - cc = true - or - // flow out of a callable - fwdFlowOut(_, node, false, config) and - cc = false - or - // flow through a callable - exists(DataFlowCall call | - fwdFlowOutFromArg(call, node, config) and - fwdFlowIsEntered(call, cc, config) - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowIn( - DataFlowCall call, NodeEx arg, Cc cc, ParamNodeEx p, Configuration config - ) { - // call context cannot help reduce virtual dispatch - fwdFlow(arg, cc, config) and - viableParamArgEx(call, p, arg) and - not fullBarrier(p, config) and - ( - cc = false - or - cc = true and - not reducedViableImplInCallContext(call, _, _) - ) - or - // call context may help reduce virtual dispatch - exists(DataFlowCallable target | - fwdFlowInReducedViableImplInSomeCallContext(call, arg, p, target, config) and - target = viableImplInSomeFwdFlowCallContextExt(call, config) and - cc = true - ) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - fwdFlowIn(call, _, cc, _, config) - } - - pragma[nomagic] - private predicate fwdFlowInReducedViableImplInSomeCallContext( - DataFlowCall call, NodeEx arg, ParamNodeEx p, DataFlowCallable target, Configuration config - ) { - fwdFlow(arg, true, config) and - viableParamArgEx(call, p, arg) and - reducedViableImplInCallContext(call, _, _) and - target = p.getEnclosingCallable() and - not fullBarrier(p, config) - } - - /** - * Gets a viable dispatch target of `call` in the context `ctx`. This is - * restricted to those `call`s for which a context might make a difference, - * and to `ctx`s that are reachable in `fwdFlow`. - */ - pragma[nomagic] - private DataFlowCallable viableImplInSomeFwdFlowCallContextExt( - DataFlowCall call, Configuration config - ) { - exists(DataFlowCall ctx | - fwdFlowIsEntered(ctx, _, config) and - result = viableImplInCallContextExt(call, ctx) - ) - } - - private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) } - - pragma[nomagic] - private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc, Configuration config) { - exists(NodeEx mid | - fwdFlow(mid, cc, config) and - readSet(mid, c, node, config) - ) - } - - /** - * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - fwdFlow(mid, _, config) and - store(mid, tc, node, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `cs` may be interpreted in a read as the target of some store - * into `c`, in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCandSet(ContentSet cs, Content c, Configuration config) { - fwdFlowConsCand(c, config) and - c = cs.getAReadContent() - } - - pragma[nomagic] - private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { - exists(RetNodeEx ret | - fwdFlow(ret, cc, config) and - ret.getReturnPosition() = pos - ) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) { - exists(ReturnPosition pos | - fwdFlowReturnPosition(pos, cc, config) and - viableReturnPosOutEx(call, pos, out) and - not fullBarrier(out, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) { - fwdFlowOut(call, out, true, config) - } - - private predicate stateStepFwd(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1 | - additionalLocalStateStep(node1, state1, _, state2, config) or - additionalJumpStateStep(node1, state1, _, state2, config) - | - fwdFlow(node1, config) - ) - } - - private predicate fwdFlowState(FlowState state, Configuration config) { - sourceNode(_, state, config) - or - exists(FlowState state0 | - fwdFlowState(state0, config) and - stateStepFwd(state0, state, config) - ) - } - - /** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink. - */ - pragma[nomagic] - private predicate revFlow(NodeEx node, boolean toReturn, Configuration config) { - revFlow0(node, toReturn, config) and - fwdFlow(node, config) - } - - pragma[nomagic] - private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) { - exists(FlowState state | - fwdFlow(node, pragma[only_bind_into](config)) and - sinkNode(node, state, config) and - fwdFlowState(state, pragma[only_bind_into](config)) and - if hasSinkCallCtx(config) then toReturn = true else toReturn = false - ) - or - exists(NodeEx mid | revFlow(mid, toReturn, config) | - localFlowStep(node, mid, config) or - additionalLocalFlowStep(node, mid, config) or - additionalLocalStateStep(node, _, mid, _, config) - ) - or - exists(NodeEx mid | revFlow(mid, _, config) and toReturn = false | - jumpStep(node, mid, config) or - additionalJumpStep(node, mid, config) or - additionalJumpStateStep(node, _, mid, _, config) - ) - or - // store - exists(Content c | - revFlowStore(c, node, toReturn, config) and - revFlowConsCand(c, config) - ) - or - // read - exists(NodeEx mid, ContentSet c | - readSet(node, c, mid, config) and - fwdFlowConsCandSet(c, _, pragma[only_bind_into](config)) and - revFlow(mid, toReturn, pragma[only_bind_into](config)) - ) - or - // flow into a callable - revFlowIn(_, node, false, config) and - toReturn = false - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(pos, config) and - node.(RetNodeEx).getReturnPosition() = pos and - toReturn = true - ) - or - // flow through a callable - exists(DataFlowCall call | - revFlowInToReturn(call, node, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - /** - * Holds if `c` is the target of a read in the flow covered by `revFlow`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, ContentSet cs | - fwdFlow(node, pragma[only_bind_into](config)) and - readSet(node, cs, mid, config) and - fwdFlowConsCandSet(cs, c, pragma[only_bind_into](config)) and - revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) { - exists(NodeEx mid, TypedContent tc | - revFlow(mid, toReturn, pragma[only_bind_into](config)) and - fwdFlowConsCand(c, pragma[only_bind_into](config)) and - store(node, tc, mid, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `c` is the target of both a read and a store in the flow covered - * by `revFlow`. - */ - pragma[nomagic] - additional predicate revFlowIsReadAndStored(Content c, Configuration conf) { - revFlowConsCand(c, conf) and - revFlowStore(c, _, _, conf) - } - - pragma[nomagic] - additional predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config - ) { - fwdFlowReturnPosition(pos, _, config) and - viableReturnPosOutEx(call, pos, out) - } - - pragma[nomagic] - private predicate revFlowOut(ReturnPosition pos, Configuration config) { - exists(NodeEx out | - revFlow(out, _, config) and - viableReturnPosOutNodeCandFwd1(_, pos, out, config) - ) - } - - pragma[nomagic] - additional predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config - ) { - fwdFlowIn(call, arg, _, p, config) - } - - // inline to reduce the number of iterations - pragma[inline] - private predicate revFlowIn( - DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config - ) { - exists(ParamNodeEx p | - revFlow(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) { - revFlowIn(call, arg, true, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(NodeEx out | - revFlow(out, toReturn, config) and - fwdFlowOutFromArg(call, out, config) - ) - } - - private predicate stateStepRev(FlowState state1, FlowState state2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - additionalLocalStateStep(node1, state1, node2, state2, config) or - additionalJumpStateStep(node1, state1, node2, state2, config) - | - revFlow(node1, _, pragma[only_bind_into](config)) and - revFlow(node2, _, pragma[only_bind_into](config)) and - fwdFlowState(state1, pragma[only_bind_into](config)) and - fwdFlowState(state2, pragma[only_bind_into](config)) - ) - } - - additional predicate revFlowState(FlowState state, Configuration config) { - exists(NodeEx node | - sinkNode(node, state, config) and - revFlow(node, _, pragma[only_bind_into](config)) and - fwdFlowState(state, pragma[only_bind_into](config)) - ) - or - exists(FlowState state0 | - revFlowState(state0, config) and - stateStepRev(state, state0, config) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Content c | - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - revFlow(node2, pragma[only_bind_into](config)) and - store(node1, tc, node2, contentType, config) and - c = tc.getContent() and - exists(ap1) - ) - } - - pragma[nomagic] - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) { - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - read(n1, c, n2, pragma[only_bind_into](config)) and - revFlow(n2, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, config) and - exists(ap) - } - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, _, pragma[only_bind_into](config)) and - exists(state) and - exists(ap) - } - - private predicate throughFlowNodeCand(NodeEx node, Configuration config) { - revFlow(node, true, config) and - fwdFlow(node, true, config) and - not inBarrier(node, config) and - not outBarrier(node, config) - } - - /** Holds if flow may return from `callable`. */ - pragma[nomagic] - private predicate returnFlowCallableNodeCand( - DataFlowCallable callable, ReturnKindExt kind, Configuration config - ) { - exists(RetNodeEx ret | - throughFlowNodeCand(ret, config) and - callable = ret.getEnclosingCallable() and - kind = ret.getKind() - ) - } - - /** - * Holds if flow may enter through `p` and reach a return node making `p` a - * candidate for the origin of a summary. - */ - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(DataFlowCallable c, ReturnKindExt kind | - throughFlowNodeCand(p, config) and - returnFlowCallableNodeCand(c, kind, config) and - p.getEnclosingCallable() = c and - exists(ap) and - parameterFlowThroughAllowed(p, kind) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - throughFlowNodeCand(ret, config) and - kind = ret.getKind() and - exists(argAp) and - exists(ap) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, boolean toReturn | - revFlow(arg, toReturn, config) and - revFlowInToReturn(call, arg, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, config)) and - fields = count(Content f0 | fwdFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | fwdFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, config)) and - fields = count(Content f0 | revFlowConsCand(f0, config)) and - conscand = -1 and - states = count(FlowState state | revFlowState(state, config)) and - tuples = count(NodeEx n, boolean b | revFlow(n, b, config)) - } - /* End: Stage 1 logic. */ -} - -pragma[noinline] -private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - localFlowStep(node1, node2, config) -} - -pragma[noinline] -private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - additionalLocalFlowStep(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCand1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config -) { - Stage1::revFlow(out, config) and - Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, Configuration config -) { - exists(ReturnPosition pos | - viableReturnPosOutNodeCand1(call, pos, out, config) and - pos = ret.getReturnPosition() and - kind = pos.getKind() and - Stage1::revFlow(ret, config) and - not outBarrier(ret, config) and - not inBarrier(out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCand1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config -) { - Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and - Stage1::revFlow(arg, config) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config -) { - viableParamArgNodeCand1(call, p, arg, config) and - Stage1::revFlow(p, config) and - not outBarrier(arg, config) and - not inBarrier(p, config) -} - -/** - * Gets the amount of forward branching on the origin of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int branch(NodeEx n1, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n1, _, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) - ) -} - -/** - * Gets the amount of backward branching on the target of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -pragma[nomagic] -private int join(NodeEx n2, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n, _, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) - ) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. The - * `allowsFieldFlow` flag indicates whether the branching is within the limit - * specified by the configuration. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, ret, kind, out, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(ret, pragma[only_bind_into](config)) and - j = join(out, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. The `allowsFieldFlow` flag indicates whether - * the branching is within the limit specified by the configuration. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config -) { - flowIntoCallNodeCand1(call, arg, p, pragma[only_bind_into](config)) and - exists(int b, int j | - b = branch(arg, pragma[only_bind_into](config)) and - j = join(p, pragma[only_bind_into](config)) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -private signature module StageSig { - class Ap; - - predicate revFlow(NodeEx node, Configuration config); - - predicate revFlowAp(NodeEx node, Ap ap, Configuration config); - - bindingset[node, state, config] - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config); - - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config); - - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config); - - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ); - - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ); - - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config); -} - -private module MkStage { - class ApApprox = PrevStage::Ap; - - signature module StageParam { - class Ap; - - class ApNil extends Ap; - - bindingset[result, ap] - ApApprox getApprox(Ap ap); - - ApNil getApNil(NodeEx node); - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail); - - /** - * An approximation of `Content` that corresponds to the precision level of - * `Ap`, such that the mappings from both `Ap` and `Content` to this type - * are functional. - */ - class ApHeadContent; - - ApHeadContent getHeadContent(Ap ap); - - ApHeadContent projectToHeadContent(Content c); - - class ApOption; - - ApOption apNone(); - - ApOption apSome(Ap ap); - - class Cc; - - class CcCall extends Cc; - - // TODO: member predicate on CcCall - predicate matchesCall(CcCall cc, DataFlowCall call); - - class CcNoCall extends Cc; - - Cc ccNone(); - - CcCall ccSomeCall(); - - class LocalCc; - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc); - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc); - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc); - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ); - - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - Configuration config - ); - - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config - ); - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config); - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType); - } - - module Stage implements StageSig { - import Param - - /* Begin: Stage logic. */ - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - private predicate revFlowApAlias(NodeEx node, ApApprox apa, Configuration config) { - PrevStage::revFlowAp(node, apa, config) - } - - pragma[nomagic] - private predicate flowIntoCallApa( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, ApApprox apa, - Configuration config - ) { - flowIntoCall(call, arg, p, allowsFieldFlow, config) and - PrevStage::revFlowAp(p, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(arg, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowOutOfCallApa( - DataFlowCall call, RetNodeEx ret, ReturnKindExt kind, NodeEx out, boolean allowsFieldFlow, - ApApprox apa, Configuration config - ) { - flowOutOfCall(call, ret, kind, out, allowsFieldFlow, config) and - PrevStage::revFlowAp(out, pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - revFlowApAlias(ret, pragma[only_bind_into](apa), pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowThroughOutOfCall( - DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, - ApApprox argApa, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - flowOutOfCallApa(call, ret, kind, out, allowsFieldFlow, apa, pragma[only_bind_into](config)) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) and - matchesCall(ccc, call) - ) - } - - /** - * Holds if `node` is reachable with access path `ap` from a source in the - * configuration `config`. - * - * The call context `cc` records whether the node is reached through an - * argument in a call, and if so, `summaryCtx` and `argAp` record the - * corresponding parameter position and access path of that argument, respectively. - */ - pragma[nomagic] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - fwdFlow0(node, state, cc, summaryCtx, argAp, ap, apa, config) and - PrevStage::revFlow(node, state, apa, config) and - filter(node, state, ap, config) - } - - pragma[inline] - additional predicate fwdFlow( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, cc, summaryCtx, argAp, ap, _, config) - } - - pragma[nomagic] - private predicate fwdFlow0( - NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap, - ApApprox apa, Configuration config - ) { - sourceNode(node, state, config) and - (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and - argAp = apNone() and - summaryCtx = TParamNodeNone() and - ap = getApNil(node) and - apa = getApprox(ap) - or - exists(NodeEx mid, FlowState state0, Ap ap0, ApApprox apa0, LocalCc localCc | - fwdFlow(mid, state0, cc, summaryCtx, argAp, ap0, apa0, config) and - localCc = getLocalCc(mid, cc) - | - localStep(mid, state0, node, state, true, _, config, localCc) and - ap = ap0 and - apa = apa0 - or - localStep(mid, state0, node, state, false, ap, config, localCc) and - ap0 instanceof ApNil and - apa = getApprox(ap) - ) - or - exists(NodeEx mid | - fwdFlow(mid, pragma[only_bind_into](state), _, _, _, ap, apa, pragma[only_bind_into](config)) and - jumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(mid, state, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStep(mid, node, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(mid, state0, _, _, _, nil, pragma[only_bind_into](config)) and - additionalJumpStateStep(mid, state0, node, state, config) and - cc = ccNone() and - summaryCtx = TParamNodeNone() and - argAp = apNone() and - ap = getApNil(node) and - apa = getApprox(ap) - ) - or - // store - exists(TypedContent tc, Ap ap0 | - fwdFlowStore(_, ap0, tc, node, state, cc, summaryCtx, argAp, config) and - ap = apCons(tc, ap0) and - apa = getApprox(ap) - ) - or - // read - exists(Ap ap0, Content c | - fwdFlowRead(ap0, c, _, node, state, cc, summaryCtx, argAp, config) and - fwdFlowConsCand(ap0, c, ap, config) and - apa = getApprox(ap) - ) - or - // flow into a callable - fwdFlowIn(_, node, state, _, cc, _, _, ap, apa, config) and - if PrevStage::parameterMayFlowThrough(node, apa, config) - then ( - summaryCtx = TParamNodeSome(node.asNode()) and - argAp = apSome(ap) - ) else ( - summaryCtx = TParamNodeNone() and argAp = apNone() - ) - or - // flow out of a callable - exists( - DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, - DataFlowCallable inner - | - fwdFlow(ret, state, innercc, summaryCtx, argAp, ap, apa, config) and - flowOutOfCallApa(call, ret, _, node, allowsFieldFlow, apa, config) and - inner = ret.getEnclosingCallable() and - cc = getCallContextReturn(inner, call, innercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - or - // flow through a callable - exists( - DataFlowCall call, CcCall ccc, RetNodeEx ret, boolean allowsFieldFlow, ApApprox innerArgApa - | - fwdFlowThrough(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, node, allowsFieldFlow, innerArgApa, apa, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowStore( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(DataFlowType contentType, ApApprox apa1 | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap1, apa1, config) and - PrevStage::storeStepCand(node1, apa1, tc, node2, contentType, config) and - typecheckStore(ap1, contentType) - ) - } - - /** - * Holds if forward flow with access path `tail` reaches a store of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(TypedContent tc | - fwdFlowStore(_, tail, tc, _, _, _, _, _, config) and - tc.getContent() = c and - cons = apCons(tc, tail) - ) - } - - pragma[nomagic] - private predicate readStepCand( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - PrevStage::readStepCand(node1, c, node2, config) and - apc = projectToHeadContent(c) - } - - bindingset[node1, apc] - pragma[inline_late] - private predicate readStepCand0( - NodeEx node1, ApHeadContent apc, Content c, NodeEx node2, Configuration config - ) { - readStepCand(node1, apc, c, node2, config) - } - - pragma[nomagic] - private predicate fwdFlowRead( - Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, - ParamNodeOption summaryCtx, ApOption argAp, Configuration config - ) { - exists(ApHeadContent apc | - fwdFlow(node1, state, cc, summaryCtx, argAp, ap, config) and - apc = getHeadContent(ap) and - readStepCand0(node1, apc, c, node2, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowIn( - DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, CcCall innercc, - ParamNodeOption summaryCtx, ApOption argAp, Ap ap, ApApprox apa, Configuration config - ) { - exists(ArgNodeEx arg, boolean allowsFieldFlow | - fwdFlow(arg, state, outercc, summaryCtx, argAp, ap, apa, config) and - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate fwdFlowRetFromArg( - RetNodeEx ret, FlowState state, CcCall ccc, ParamNodeEx summaryCtx, Ap argAp, ApApprox argApa, - Ap ap, ApApprox apa, Configuration config - ) { - exists(ReturnKindExt kind | - fwdFlow(pragma[only_bind_into](ret), state, ccc, - TParamNodeSome(pragma[only_bind_into](summaryCtx.asNode())), - pragma[only_bind_into](apSome(argAp)), ap, pragma[only_bind_into](apa), - pragma[only_bind_into](config)) and - kind = ret.getKind() and - parameterFlowThroughAllowed(summaryCtx, kind) and - argApa = getApprox(argAp) and - PrevStage::returnMayFlowThrough(ret, argApa, apa, kind, pragma[only_bind_into](config)) - ) - } - - pragma[inline] - private predicate fwdFlowThrough0( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ParamNodeEx innerSummaryCtx, - Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowRetFromArg(ret, state, ccc, innerSummaryCtx, innerArgAp, innerArgApa, ap, apa, config) and - fwdFlowIsEntered(call, cc, ccc, summaryCtx, argAp, innerSummaryCtx, innerArgAp, config) - } - - pragma[nomagic] - private predicate fwdFlowThrough( - DataFlowCall call, Cc cc, FlowState state, CcCall ccc, ParamNodeOption summaryCtx, - ApOption argAp, Ap ap, ApApprox apa, RetNodeEx ret, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, cc, state, ccc, summaryCtx, argAp, ap, apa, ret, _, _, innerArgApa, - config) - } - - /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` - * and data might flow through the target callable and back out at `call`. - */ - pragma[nomagic] - private predicate fwdFlowIsEntered( - DataFlowCall call, Cc cc, CcCall innerCc, ParamNodeOption summaryCtx, ApOption argAp, - ParamNodeEx p, Ap ap, Configuration config - ) { - exists(ApApprox apa | - fwdFlowIn(call, pragma[only_bind_into](p), _, cc, innerCc, summaryCtx, argAp, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(p, apa, config) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate storeStepFwd( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config - ) { - fwdFlowStore(node1, ap1, tc, node2, _, _, _, _, config) and - ap2 = apCons(tc, ap1) and - fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, _, config) - } - - private predicate readStepFwd( - NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config - ) { - fwdFlowRead(ap1, c, n1, n2, _, _, _, _, config) and - fwdFlowConsCand(ap1, c, ap2, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough0( - DataFlowCall call, FlowState state, CcCall ccc, Ap ap, ApApprox apa, RetNodeEx ret, - ParamNodeEx innerSummaryCtx, Ap innerArgAp, ApApprox innerArgApa, Configuration config - ) { - fwdFlowThrough0(call, _, state, ccc, _, _, ap, apa, ret, innerSummaryCtx, innerArgAp, - innerArgApa, config) - } - - pragma[nomagic] - private predicate returnFlowsThrough( - RetNodeEx ret, ReturnPosition pos, FlowState state, CcCall ccc, ParamNodeEx p, Ap argAp, - Ap ap, Configuration config - ) { - exists(DataFlowCall call, ApApprox apa, boolean allowsFieldFlow, ApApprox innerArgApa | - returnFlowsThrough0(call, state, ccc, ap, apa, ret, p, argAp, innerArgApa, config) and - flowThroughOutOfCall(call, ccc, ret, _, allowsFieldFlow, innerArgApa, apa, config) and - pos = ret.getReturnPosition() and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowThroughIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap argAp, Ap ap, - Configuration config - ) { - exists(ApApprox argApa | - flowIntoCallApa(call, pragma[only_bind_into](arg), pragma[only_bind_into](p), - allowsFieldFlow, argApa, pragma[only_bind_into](config)) and - fwdFlow(arg, _, _, _, _, pragma[only_bind_into](argAp), argApa, - pragma[only_bind_into](config)) and - returnFlowsThrough(_, _, _, _, p, pragma[only_bind_into](argAp), ap, - pragma[only_bind_into](config)) and - if allowsFieldFlow = false then argAp instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate flowIntoCallAp( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap, - Configuration config - ) { - exists(ApApprox apa | - flowIntoCallApa(call, arg, p, allowsFieldFlow, apa, config) and - fwdFlow(arg, _, _, _, _, ap, apa, config) - ) - } - - pragma[nomagic] - private predicate flowOutOfCallAp( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, NodeEx out, boolean allowsFieldFlow, - Ap ap, Configuration config - ) { - exists(ApApprox apa | - flowOutOfCallApa(call, ret, _, out, allowsFieldFlow, apa, config) and - fwdFlow(ret, _, _, _, _, ap, apa, config) and - pos = ret.getReturnPosition() - ) - } - - /** - * Holds if `node` with access path `ap` is part of a path from a source to a - * sink in the configuration `config`. - * - * The parameter `returnCtx` records whether (and how) the node must be returned - * from the enclosing callable in order to reach a sink, and if so, `returnAp` - * records the access path of the returned value. - */ - pragma[nomagic] - additional predicate revFlow( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - revFlow0(node, state, returnCtx, returnAp, ap, config) and - fwdFlow(node, state, _, _, _, ap, config) - } - - pragma[nomagic] - private predicate revFlow0( - NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap, - Configuration config - ) { - fwdFlow(node, state, _, _, _, ap, config) and - sinkNode(node, state, config) and - ( - if hasSinkCallCtx(config) - then returnCtx = TReturnCtxNoFlowThrough() - else returnCtx = TReturnCtxNone() - ) and - returnAp = apNone() and - ap instanceof ApNil - or - exists(NodeEx mid, FlowState state0 | - localStep(node, state, mid, state0, true, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, ap, config) - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, pragma[only_bind_into](state), _, _, _, ap, pragma[only_bind_into](config)) and - localStep(node, pragma[only_bind_into](state), mid, state0, false, _, config, _) and - revFlow(mid, state0, returnCtx, returnAp, nil, pragma[only_bind_into](config)) and - ap instanceof ApNil - ) - or - exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, state, _, _, ap, config) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStep(node, mid, config) and - revFlow(pragma[only_bind_into](mid), state, _, _, nil, pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - exists(NodeEx mid, FlowState state0, ApNil nil | - fwdFlow(node, _, _, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStateStep(node, state, mid, state0, config) and - revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil, - pragma[only_bind_into](config)) and - returnCtx = TReturnCtxNone() and - returnAp = apNone() and - ap instanceof ApNil - ) - or - // store - exists(Ap ap0, Content c | - revFlowStore(ap0, c, ap, node, state, _, _, returnCtx, returnAp, config) and - revFlowConsCand(ap0, c, ap, config) - ) - or - // read - exists(NodeEx mid, Ap ap0 | - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - readStepFwd(node, ap, _, mid, ap0, config) - ) - or - // flow into a callable - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, state, TReturnCtxNone(), returnAp, ap, config) and - flowIntoCallAp(_, node, p, allowsFieldFlow, ap, config) and - (if allowsFieldFlow = false then ap instanceof ApNil else any()) and - returnCtx = TReturnCtxNone() - ) - or - // flow through a callable - exists(DataFlowCall call, ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, node, p, _, ap, innerReturnAp, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - revFlowOut(_, node, pos, state, _, _, ap, config) and - if returnFlowsThrough(node, pos, state, _, _, _, ap, config) - then ( - returnCtx = TReturnCtxMaybeFlowThrough(pos) and - returnAp = apSome(ap) - ) else ( - returnCtx = TReturnCtxNoFlowThrough() and returnAp = apNone() - ) - ) - } - - pragma[nomagic] - private predicate revFlowStore( - Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid, - ReturnCtx returnCtx, ApOption returnAp, Configuration config - ) { - revFlow(mid, state, returnCtx, returnAp, ap0, config) and - storeStepFwd(node, ap, tc, mid, ap0, config) and - tc.getContent() = c - } - - /** - * Holds if reverse flow with access path `tail` reaches a read of `c` - * resulting in access path `cons`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { - exists(NodeEx mid, Ap tail0 | - revFlow(mid, _, _, _, tail, config) and - tail = pragma[only_bind_into](tail0) and - readStepFwd(_, cons, c, mid, tail0, config) - ) - } - - pragma[nomagic] - private predicate revFlowOut( - DataFlowCall call, RetNodeEx ret, ReturnPosition pos, FlowState state, ReturnCtx returnCtx, - ApOption returnAp, Ap ap, Configuration config - ) { - exists(NodeEx out, boolean allowsFieldFlow | - revFlow(out, state, returnCtx, returnAp, ap, config) and - flowOutOfCallAp(call, ret, pos, out, allowsFieldFlow, ap, config) and - if allowsFieldFlow = false then ap instanceof ApNil else any() - ) - } - - pragma[nomagic] - private predicate revFlowParamToReturn( - ParamNodeEx p, FlowState state, ReturnPosition pos, Ap returnAp, Ap ap, Configuration config - ) { - revFlow(pragma[only_bind_into](p), state, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), - pragma[only_bind_into](ap), pragma[only_bind_into](config)) and - parameterFlowThroughAllowed(p, pos.getKind()) and - PrevStage::parameterMayFlowThrough(p, getApprox(ap), config) - } - - pragma[nomagic] - private predicate revFlowThrough( - DataFlowCall call, ReturnCtx returnCtx, ParamNodeEx p, FlowState state, ReturnPosition pos, - ApOption returnAp, Ap ap, Ap innerReturnAp, Configuration config - ) { - revFlowParamToReturn(p, state, pos, innerReturnAp, ap, config) and - revFlowIsReturned(call, returnCtx, returnAp, pos, innerReturnAp, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned( - DataFlowCall call, ReturnCtx returnCtx, ApOption returnAp, ReturnPosition pos, Ap ap, - Configuration config - ) { - exists(RetNodeEx ret, FlowState state, CcCall ccc | - revFlowOut(call, ret, pos, state, returnCtx, returnAp, ap, config) and - returnFlowsThrough(ret, pos, state, ccc, _, _, ap, config) and - matchesCall(ccc, call) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Ap ap2, Content c | - PrevStage::storeStepCand(node1, _, tc, node2, contentType, config) and - revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _, config) and - revFlowConsCand(ap2, c, ap1, config) - ) - } - - predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(Ap ap1, Ap ap2 | - revFlow(node2, _, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and - readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _, - pragma[only_bind_into](config)) - ) - } - - additional predicate revFlow(NodeEx node, FlowState state, Configuration config) { - revFlow(node, state, _, _, _, config) - } - - predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, _, _, ap, config) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) } - - pragma[nomagic] - predicate revFlowAp(NodeEx node, Ap ap, Configuration config) { - revFlow(node, _, _, _, ap, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, Configuration config) { - revFlow(node, _, _, _, _, config) - } - - // use an alias as a workaround for bad functionality-induced joins - pragma[nomagic] - additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) { - revFlow(node, state, ap, config) - } - - private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepFwd(_, ap, tc, _, _, config) - } - - private predicate revConsCand(TypedContent tc, Ap ap, Configuration config) { - storeStepCand(_, ap, tc, _, _, config) - } - - private predicate validAp(Ap ap, Configuration config) { - revFlow(_, _, _, _, ap, config) and ap instanceof ApNil - or - exists(TypedContent head, Ap tail | - consCand(head, tail, config) and - ap = apCons(head, tail) - ) - } - - additional predicate consCand(TypedContent tc, Ap ap, Configuration config) { - revConsCand(tc, ap, config) and - validAp(ap, config) - } - - pragma[nomagic] - private predicate parameterFlowsThroughRev( - ParamNodeEx p, Ap ap, ReturnPosition pos, Ap returnAp, Configuration config - ) { - revFlow(p, _, TReturnCtxMaybeFlowThrough(pos), apSome(returnAp), ap, config) and - parameterFlowThroughAllowed(p, pos.getKind()) - } - - pragma[nomagic] - predicate parameterMayFlowThrough(ParamNodeEx p, Ap ap, Configuration config) { - exists(ReturnPosition pos | - returnFlowsThrough(_, pos, _, _, p, ap, _, config) and - parameterFlowsThroughRev(p, ap, pos, _, config) - ) - } - - pragma[nomagic] - predicate returnMayFlowThrough( - RetNodeEx ret, Ap argAp, Ap ap, ReturnKindExt kind, Configuration config - ) { - exists(ParamNodeEx p, ReturnPosition pos | - returnFlowsThrough(ret, pos, _, _, p, argAp, ap, config) and - parameterFlowsThroughRev(p, argAp, pos, ap, config) and - kind = pos.getKind() - ) - } - - pragma[nomagic] - private predicate revFlowThroughArg( - DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, - Ap ap, Configuration config - ) { - exists(ParamNodeEx p, Ap innerReturnAp | - revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp, config) and - flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp, config) - ) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | - revFlow(arg, state, returnCtx, returnAp, ap, config) and - revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap, config) - ) - } - - additional predicate stats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config - ) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, config)) and - fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and - states = count(FlowState state | fwdFlow(_, state, _, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, ApOption argAp, Ap ap | - fwdFlow(n, state, cc, summaryCtx, argAp, ap, config) - ) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, _, _, _, config)) and - fields = count(TypedContent f0 | consCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and - states = count(FlowState state | revFlow(_, state, _, _, _, config)) and - tuples = - count(NodeEx n, FlowState state, ReturnCtx returnCtx, ApOption retAp, Ap ap | - revFlow(n, state, returnCtx, retAp, ap, config) - ) - } - /* End: Stage logic. */ - } -} - -private module BooleanCallContext { - class Cc extends boolean { - Cc() { this in [true, false] } - } - - class CcCall extends Cc { - CcCall() { this = true } - } - - /** Holds if the call context may be `call`. */ - predicate matchesCall(CcCall cc, DataFlowCall call) { any() } - - class CcNoCall extends Cc { - CcNoCall() { this = false } - } - - Cc ccNone() { result = false } - - CcCall ccSomeCall() { result = true } - - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } -} - -private module Level1CallContext { - class Cc = CallContext; - - class CcCall = CallContextCall; - - pragma[inline] - predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) } - - class CcNoCall = CallContextNoCall; - - Cc ccNone() { result instanceof CallContextAny } - - CcCall ccSomeCall() { result instanceof CallContextSomeCall } - - module NoLocalCallContext { - class LocalCc = Unit; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { any() } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSiteDispatch(call, c) - then result = TSpecificCall(call) - else result = TSomeCall() - } - } - - module LocalCallContext { - class LocalCc = LocalCallContext; - - bindingset[node, cc] - LocalCc getLocalCc(NodeEx node, Cc cc) { - result = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - node.getEnclosingCallable()) - } - - bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() - } - } - - bindingset[call, c, innercc] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { - checkCallContextReturn(innercc, c, call) and - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() - } -} - -private module Stage2Param implements MkStage::StageParam { - private module PrevStage = Stage1; - - class Ap extends boolean { - Ap() { this in [true, false] } - } - - class ApNil extends Ap { - ApNil() { this = false } - } - - bindingset[result, ap] - PrevStage::Ap getApprox(Ap ap) { any() } - - ApNil getApNil(NodeEx node) { Stage1::revFlow(node, _) and exists(result) } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } - - class ApHeadContent = Unit; - - pragma[inline] - ApHeadContent getHeadContent(Ap ap) { exists(result) and ap = true } - - ApHeadContent projectToHeadContent(Content c) { any() } - - class ApOption = BooleanOption; - - ApOption apNone() { result = TBooleanNone() } - - ApOption apSome(Ap ap) { result = TBooleanSome(ap) } - - import Level1CallContext - import NoLocalCallContext - - bindingset[node1, state1, config] - bindingset[node2, state2, config] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - ( - preservesValue = true and - localFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 - or - preservesValue = false and - additionalLocalStateStep(node1, state1, node2, state2, config) - ) and - exists(ap) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand1/6; - - predicate flowIntoCall = flowIntoCallNodeCand1/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - expectsContentEx(node, c) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - PrevStage::revFlowState(state, pragma[only_bind_into](config)) and - exists(ap) and - not stateBarrier(node, state, config) and - ( - notExpectsContent(node) - or - ap = true and - expectsContentCand(node, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage2 implements StageSig { - import MkStage::Stage -} - -pragma[nomagic] -private predicate flowOutOfCallNodeCand2( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowOutOfCallNodeCand1(call, node1, kind, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -pragma[nomagic] -private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node1, pragma[only_bind_into](config)) -} - -private module LocalFlowBigStep { - /** - * A node where some checking is required, and hence the big-step relation - * is not allowed to step over. - */ - private class FlowCheckNode extends NodeEx { - FlowCheckNode() { - castNode(this.asNode()) or - clearsContentCached(this.asNode(), _) or - expectsContentCached(this.asNode(), _) - } - } - - /** - * Holds if `node` can be the first node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowEntry(NodeEx node, FlowState state, Configuration config) { - Stage2::revFlow(node, state, config) and - ( - sourceNode(node, state, config) - or - jumpStep(_, node, config) - or - additionalJumpStep(_, node, config) - or - additionalJumpStateStep(_, _, node, state, config) - or - node instanceof ParamNodeEx - or - node.asNode() instanceof OutNodeExt - or - Stage2::storeStepCand(_, _, _, node, _, config) - or - Stage2::readStepCand(_, _, node, config) - or - node instanceof FlowCheckNode - or - exists(FlowState s | - additionalLocalStateStep(_, s, node, state, config) and - s != state - ) - ) - } - - /** - * Holds if `node` can be the last node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowExit(NodeEx node, FlowState state, Configuration config) { - exists(NodeEx next | Stage2::revFlow(next, state, config) | - jumpStep(node, next, config) or - additionalJumpStep(node, next, config) or - flowIntoCallNodeCand2(_, node, next, _, config) or - flowOutOfCallNodeCand2(_, node, _, next, _, config) or - Stage2::storeStepCand(node, _, _, next, _, config) or - Stage2::readStepCand(node, _, next, config) - ) - or - exists(NodeEx next, FlowState s | Stage2::revFlow(next, s, config) | - additionalJumpStateStep(node, state, next, s, config) - or - additionalLocalStateStep(node, state, next, s, config) and - s != state - ) - or - Stage2::revFlow(node, state, config) and - node instanceof FlowCheckNode - or - sinkNode(node, state, config) - } - - pragma[noinline] - private predicate additionalLocalFlowStepNodeCand2( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, Configuration config - ) { - additionalLocalFlowStepNodeCand1(node1, node2, config) and - state1 = state2 and - Stage2::revFlow(node1, pragma[only_bind_into](state1), false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false, - pragma[only_bind_into](config)) - or - additionalLocalStateStep(node1, state1, node2, state2, config) and - Stage2::revFlow(node1, state1, false, pragma[only_bind_into](config)) and - Stage2::revFlowAlias(node2, state2, false, pragma[only_bind_into](config)) - } - - /** - * Holds if the local path from `node1` to `node2` is a prefix of a maximal - * subsequence of local flow steps in a dataflow path. - * - * This is the transitive closure of `[additional]localFlowStep` beginning - * at `localFlowEntry`. - */ - pragma[nomagic] - private predicate localFlowStepPlus( - NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, - Configuration config, LocalCallContext cc - ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and - ( - localFlowEntry(node1, pragma[only_bind_into](state), pragma[only_bind_into](config)) and - ( - localFlowStepNodeCand1(node1, node2, config) and - preservesValue = true and - t = node1.getDataFlowType() and // irrelevant dummy value - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - or - additionalLocalFlowStepNodeCand2(node1, state, node2, state, config) and - preservesValue = false and - t = node2.getDataFlowType() - ) and - node1 != node2 and - cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) - or - exists(NodeEx mid | - localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, - pragma[only_bind_into](config), cc) and - localFlowStepNodeCand1(mid, node2, config) and - not mid instanceof FlowCheckNode and - Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config)) - ) - or - exists(NodeEx mid | - localFlowStepPlus(node1, state, mid, _, _, pragma[only_bind_into](config), cc) and - additionalLocalFlowStepNodeCand2(mid, state, node2, state, config) and - not mid instanceof FlowCheckNode and - preservesValue = false and - t = node2.getDataFlowType() - ) - ) - } - - /** - * Holds if `node1` can step to `node2` in one or more local steps and this - * path can occur as a maximal subsequence of local steps in a dataflow path. - */ - pragma[nomagic] - predicate localFlowBigStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - DataFlowType t, Configuration config, LocalCallContext callContext - ) { - localFlowStepPlus(node1, state1, node2, preservesValue, t, config, callContext) and - localFlowExit(node2, state1, config) and - state1 = state2 - or - additionalLocalFlowStepNodeCand2(node1, state1, node2, state2, config) and - state1 != state2 and - preservesValue = false and - t = node2.getDataFlowType() and - callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) - } -} - -private import LocalFlowBigStep - -private module Stage3Param implements MkStage::StageParam { - private module PrevStage = Stage2; - - class Ap = ApproxAccessPathFront; - - class ApNil = ApproxAccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TApproxFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getAHead() = tc and exists(tail) } - - class ApHeadContent = ContentApprox; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - predicate projectToHeadContent = getContentApprox/1; - - class ApOption = ApproxAccessPathFrontOption; - - ApOption apNone() { result = TApproxAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TApproxAccessPathFrontSome(ap) } - - import BooleanCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApproxAccessPathFrontNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - exists(lcc) - } - - predicate flowOutOfCall = flowOutOfCallNodeCand2/6; - - predicate flowIntoCall = flowIntoCallNodeCand2/5; - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getAHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage3 implements StageSig { - import MkStage::Stage -} - -private module Stage4Param implements MkStage::StageParam { - private module PrevStage = Stage3; - - class Ap = AccessPathFront; - - class ApNil = AccessPathFrontNil; - - PrevStage::Ap getApprox(Ap ap) { result = ap.toApprox() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathFrontOption; - - ApOption apNone() { result = TAccessPathFrontNone() } - - ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } - - import BooleanCallContext - - pragma[nomagic] - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, _) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) and - exists(lcc) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clearSet(NodeEx node, ContentSet c, Configuration config) { - PrevStage::revFlow(node, config) and - clearsContentCached(node.asNode(), c) - } - - pragma[nomagic] - private predicate clearContent(NodeEx node, Content c, Configuration config) { - exists(ContentSet cs | - PrevStage::readStepCand(_, pragma[only_bind_into](c), _, pragma[only_bind_into](config)) and - c = cs.getAReadContent() and - clearSet(node, cs, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate clear(NodeEx node, Ap ap, Configuration config) { - clearContent(node, ap.getHead().getContent(), config) - } - - pragma[nomagic] - private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) { - exists(Content c | - PrevStage::revFlow(node, pragma[only_bind_into](config)) and - PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and - expectsContentEx(node, c) and - c = ap.getHead().getContent() - ) - } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { - exists(state) and - exists(config) and - not clear(node, ap, config) and - (if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and - ( - notExpectsContent(node) - or - expectsContentCand(node, ap, config) - ) - } - - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap.getType(), contentType) - } -} - -private module Stage4 implements StageSig { - import MkStage::Stage -} - -/** - * Holds if `argApf` is recorded as the summary context for flow reaching `node` - * and remains relevant for the following pruning stage. - */ -private predicate flowCandSummaryCtx( - NodeEx node, FlowState state, AccessPathFront argApf, Configuration config -) { - exists(AccessPathFront apf | - Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf, config) and - Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, TAccessPathFrontSome(argApf), apf, - config) - ) -} - -/** - * Holds if a length 2 access path approximation with the head `tc` is expected - * to be expensive. - */ -private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { - exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | Stage4::consCand(tc, apf, config)) and - nodes = - strictcount(NodeEx n, FlowState state | - Stage4::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - or - flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - ) and - accessPathApproxCostLimits(apLimit, tupleLimit) and - apLimit < tails and - tupleLimit < (tails - 1) * nodes and - not tc.forceHighPrecision() - ) -} - -private newtype TAccessPathApprox = - TNil(DataFlowType t) or - TConsNil(TypedContent tc, DataFlowType t) { - Stage4::consCand(tc, TFrontNil(t), _) and - not expensiveLen2unfolding(tc, _) - } or - TConsCons(TypedContent tc1, TypedContent tc2, int len) { - Stage4::consCand(tc1, TFrontHead(tc2), _) and - len in [2 .. accessPathLimit()] and - not expensiveLen2unfolding(tc1, _) - } or - TCons1(TypedContent tc, int len) { - len in [1 .. accessPathLimit()] and - expensiveLen2unfolding(tc, _) - } - -/** - * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only - * the first two elements of the list and its length are tracked. If data flows - * from a source to a given node with a given `AccessPathApprox`, this indicates - * the sequence of dereference operations needed to get from the value in the node - * to the tracked object. The final type indicates the type of the tracked object. - */ -abstract private class AccessPathApprox extends TAccessPathApprox { - abstract string toString(); - - abstract TypedContent getHead(); - - abstract int len(); - - abstract DataFlowType getType(); - - abstract AccessPathFront getFront(); - - /** Gets the access path obtained by popping `head` from this path, if any. */ - abstract AccessPathApprox pop(TypedContent head); -} - -private class AccessPathApproxNil extends AccessPathApprox, TNil { - private DataFlowType t; - - AccessPathApproxNil() { this = TNil(t) } - - override string toString() { result = concat(": " + ppReprType(t)) } - - override TypedContent getHead() { none() } - - override int len() { result = 0 } - - override DataFlowType getType() { result = t } - - override AccessPathFront getFront() { result = TFrontNil(t) } - - override AccessPathApprox pop(TypedContent head) { none() } -} - -abstract private class AccessPathApproxCons extends AccessPathApprox { } - -private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { - private TypedContent tc; - private DataFlowType t; - - AccessPathApproxConsNil() { this = TConsNil(tc, t) } - - override string toString() { - // The `concat` becomes "" if `ppReprType` has no result. - result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) - } - - override TypedContent getHead() { result = tc } - - override int len() { result = 1 } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } -} - -private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { - private TypedContent tc1; - private TypedContent tc2; - private int len; - - AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - - override string toString() { - if len = 2 - then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" - else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc1 } - - override int len() { result = len } - - override DataFlowType getType() { result = tc1.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc1) } - - override AccessPathApprox pop(TypedContent head) { - head = tc1 and - ( - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - } -} - -private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { - private TypedContent tc; - private int len; - - AccessPathApproxCons1() { this = TCons1(tc, len) } - - override string toString() { - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc } - - override int len() { result = len } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { - head = tc and - ( - exists(TypedContent tc2 | Stage4::consCand(tc, TFrontHead(tc2), _) | - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - or - exists(DataFlowType t | - len = 1 and - Stage4::consCand(tc, TFrontNil(t), _) and - result = TNil(t) - ) - ) - } -} - -/** Gets the access path obtained by popping `tc` from `ap`, if any. */ -private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } - -/** Gets the access path obtained by pushing `tc` onto `ap`. */ -private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } - -private newtype TAccessPathApproxOption = - TAccessPathApproxNone() or - TAccessPathApproxSome(AccessPathApprox apa) - -private class AccessPathApproxOption extends TAccessPathApproxOption { - string toString() { - this = TAccessPathApproxNone() and result = "" - or - this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) - } -} - -private module Stage5Param implements MkStage::StageParam { - private module PrevStage = Stage4; - - class Ap = AccessPathApprox; - - class ApNil = AccessPathApproxNil; - - pragma[nomagic] - PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() } - - ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) - } - - bindingset[tc, tail] - Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } - - class ApHeadContent = Content; - - pragma[noinline] - ApHeadContent getHeadContent(Ap ap) { result = ap.getHead().getContent() } - - ApHeadContent projectToHeadContent(Content c) { result = c } - - class ApOption = AccessPathApproxOption; - - ApOption apNone() { result = TAccessPathApproxNone() } - - ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } - - import Level1CallContext - import LocalCallContext - - predicate localStep( - NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, - ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getType(), config, lcc) and - PrevStage::revFlow(node1, pragma[only_bind_into](state1), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node2, pragma[only_bind_into](state2), _, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, ReturnKindExt kind, NodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowOutOfCallNodeCand2(call, node1, kind, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - exists(FlowState state | - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and - PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _, - pragma[only_bind_into](config)) - ) - } - - bindingset[node, state, ap, config] - predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { any() } - - // Type checking is not necessary here as it has already been done in stage 3. - bindingset[ap, contentType] - predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } -} - -private module Stage5 = MkStage::Stage; - -bindingset[conf, result] -private Configuration unbindConf(Configuration conf) { - exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c)) -} - -pragma[nomagic] -private predicate nodeMayUseSummary0( - NodeEx n, ParamNodeEx p, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(AccessPathApprox apa0 | - Stage5::parameterMayFlowThrough(p, _, _) and - Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0, config) and - Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), - TAccessPathApproxSome(apa), apa0, config) - ) -} - -pragma[nomagic] -private predicate nodeMayUseSummary( - NodeEx n, FlowState state, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::parameterMayFlowThrough(p, apa, config) and - nodeMayUseSummary0(n, p, state, apa, config) - ) -} - -private newtype TSummaryCtx = - TSummaryCtxNone() or - TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) { - exists(Configuration config | - Stage5::parameterMayFlowThrough(p, ap.getApprox(), config) and - Stage5::revFlow(p, state, _, config) - ) - } - -/** - * A context for generating flow summaries. This represents flow entry through - * a specific parameter with an access path of a specific shape. - * - * Summaries are only created for parameters that may flow through. - */ -abstract private class SummaryCtx extends TSummaryCtx { - abstract string toString(); -} - -/** A summary context from which no flow summary can be generated. */ -private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { - override string toString() { result = "" } -} - -/** A summary context from which a flow summary can be generated. */ -private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParamNodeEx p; - private FlowState s; - private AccessPath ap; - - SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) } - - ParameterPosition getParameterPos() { p.isParameterOf(_, result) } - - ParamNodeEx getParamNode() { result = p } - - override string toString() { result = p + ": " + ap } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** - * Gets the number of length 2 access path approximations that correspond to `apa`. - */ -private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { - exists(TypedContent tc, int len | - tc = apa.getHead() and - len = apa.len() and - result = - strictcount(AccessPathFront apf | - Stage5::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), - config) - ) - ) -} - -private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = - strictcount(NodeEx n, FlowState state | - Stage5::revFlow(n, state, apa, config) or nodeMayUseSummary(n, state, apa, config) - ) -} - -/** - * Holds if a length 2 access path approximation matching `apa` is expected - * to be expensive. - */ -private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = count1to2unfold(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - apLimit < aps and - tupleLimit < (aps - 1) * nodes - ) -} - -private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { - exists(TypedContent head | - apa.pop(head) = result and - Stage5::consCand(head, result, config) - ) -} - -/** - * Holds with `unfold = false` if a precise head-tail representation of `apa` is - * expected to be expensive. Holds with `unfold = true` otherwise. - */ -private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { - if apa.getHead().forceHighPrecision() - then unfold = true - else - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = countPotentialAps(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true - ) -} - -/** - * Gets the number of `AccessPath`s that correspond to `apa`. - */ -pragma[assume_small_delta] -private int countAps(AccessPathApprox apa, Configuration config) { - evalUnfold(apa, false, config) and - result = 1 and - (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) - or - evalUnfold(apa, false, config) and - result = count1to2unfold(apa, config) and - not expensiveLen1to2unfolding(apa, config) - or - evalUnfold(apa, true, config) and - result = countPotentialAps(apa, config) -} - -/** - * Gets the number of `AccessPath`s that would correspond to `apa` assuming - * that it is expanded to a precise head-tail representation. - */ -language[monotonicAggregates] -pragma[assume_small_delta] -private int countPotentialAps(AccessPathApprox apa, Configuration config) { - apa instanceof AccessPathApproxNil and result = 1 - or - result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) -} - -private newtype TAccessPath = - TAccessPathNil(DataFlowType t) or - TAccessPathCons(TypedContent head, AccessPath tail) { - exists(AccessPathApproxCons apa | - not evalUnfold(apa, false, _) and - head = apa.getHead() and - tail.getApprox() = getATail(apa, _) - ) - } or - TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - not expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head1 = apa.getHead() and - head2 = getATail(apa, _).getHead() - ) - } or - TAccessPathCons1(TypedContent head, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head = apa.getHead() - ) - } - -private newtype TPathNode = - pragma[assume_small_delta] - TPathNodeMid( - NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config - ) { - // A PathNode is introduced by a source ... - Stage5::revFlow(node, state, config) and - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - or - // ... or a step from an existing PathNode to another node. - exists(PathNodeMid mid | - pathStep(mid, node, state, cc, sc, ap) and - pragma[only_bind_into](config) = mid.getConfiguration() and - Stage5::revFlow(node, state, ap.getApprox(), pragma[only_bind_into](config)) - ) - } or - TPathNodeSink(NodeEx node, FlowState state, Configuration config) { - exists(PathNodeMid sink | - sink.isAtSink() and - node = sink.getNodeEx() and - state = sink.getState() and - config = sink.getConfiguration() - ) - } or - TPathNodeSourceGroup(string sourceGroup, Configuration config) { - exists(PathNodeImpl source | - sourceGroup = source.getSourceGroup() and - config = source.getConfiguration() - ) - } or - TPathNodeSinkGroup(string sinkGroup, Configuration config) { - exists(PathNodeSink sink | - sinkGroup = sink.getSinkGroup() and - config = sink.getConfiguration() - ) - } - -/** - * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a - * source to a given node with a given `AccessPath`, this indicates the sequence - * of dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ -private class AccessPath extends TAccessPath { - /** Gets the head of this access path, if any. */ - abstract TypedContent getHead(); - - /** Gets the tail of this access path, if any. */ - abstract AccessPath getTail(); - - /** Gets the front of this access path. */ - abstract AccessPathFront getFront(); - - /** Gets the approximation of this access path. */ - abstract AccessPathApprox getApprox(); - - /** Gets the length of this access path. */ - abstract int length(); - - /** Gets a textual representation of this access path. */ - abstract string toString(); - - /** Gets the access path obtained by popping `tc` from this access path, if any. */ - final AccessPath pop(TypedContent tc) { - result = this.getTail() and - tc = this.getHead() - } - - /** Gets the access path obtained by pushing `tc` onto this access path. */ - final AccessPath push(TypedContent tc) { this = result.pop(tc) } -} - -private class AccessPathNil extends AccessPath, TAccessPathNil { - private DataFlowType t; - - AccessPathNil() { this = TAccessPathNil(t) } - - DataFlowType getType() { result = t } - - override TypedContent getHead() { none() } - - override AccessPath getTail() { none() } - - override AccessPathFrontNil getFront() { result = TFrontNil(t) } - - override AccessPathApproxNil getApprox() { result = TNil(t) } - - override int length() { result = 0 } - - override string toString() { result = concat(": " + ppReprType(t)) } -} - -private class AccessPathCons extends AccessPath, TAccessPathCons { - private TypedContent head; - private AccessPath tail; - - AccessPathCons() { this = TAccessPathCons(head, tail) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { result = tail } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - pragma[assume_small_delta] - override AccessPathApproxCons getApprox() { - result = TConsNil(head, tail.(AccessPathNil).getType()) - or - result = TConsCons(head, tail.getHead(), this.length()) - or - result = TCons1(head, this.length()) - } - - pragma[assume_small_delta] - override int length() { result = 1 + tail.length() } - - private string toStringImpl(boolean needsSuffix) { - exists(DataFlowType t | - tail = TAccessPathNil(t) and - needsSuffix = false and - result = head.toString() + "]" + concat(" : " + ppReprType(t)) - ) - or - result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) - or - exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | - result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true - or - result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false - ) - or - exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | - result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true - or - result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false - ) - } - - override string toString() { - result = "[" + this.toStringImpl(true) + this.length().toString() + ")]" - or - result = "[" + this.toStringImpl(false) - } -} - -private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { - private TypedContent head1; - private TypedContent head2; - private int len; - - AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } - - override TypedContent getHead() { result = head1 } - - override AccessPath getTail() { - Stage5::consCand(head1, result.getApprox(), _) and - result.getHead() = head2 and - result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head1) } - - override AccessPathApproxCons getApprox() { - result = TConsCons(head1, head2, len) or - result = TCons1(head1, len) - } - - override int length() { result = len } - - override string toString() { - if len = 2 - then result = "[" + head1.toString() + ", " + head2.toString() + "]" - else - result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" - } -} - -private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { - private TypedContent head; - private int len; - - AccessPathCons1() { this = TAccessPathCons1(head, len) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { - Stage5::consCand(head, result.getApprox(), _) and result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - - override int length() { result = len } - - override string toString() { - if len = 1 - then result = "[" + head.toString() + "]" - else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" - } -} - -abstract private class PathNodeImpl extends TPathNode { - /** Gets the `FlowState` of this node. */ - abstract FlowState getState(); - - /** Gets the associated configuration. */ - abstract Configuration getConfiguration(); - - /** Holds if this node is a source. */ - abstract predicate isSource(); - - abstract PathNodeImpl getASuccessorImpl(); - - private PathNodeImpl getASuccessorIfHidden() { - this.isHidden() and - result = this.getASuccessorImpl() - } - - pragma[nomagic] - private PathNodeImpl getANonHiddenSuccessor0() { - result = this.getASuccessorIfHidden*() and - not result.isHidden() - } - - final PathNodeImpl getANonHiddenSuccessor() { - result = this.getASuccessorImpl().getANonHiddenSuccessor0() and - not this.isHidden() - } - - abstract NodeEx getNodeEx(); - - predicate isHidden() { - not this.getConfiguration().includeHiddenNodes() and - ( - hiddenNode(this.getNodeEx().asNode()) and - not this.isSource() and - not this instanceof PathNodeSink - or - this.getNodeEx() instanceof TNodeImplicitRead - ) - } - - string getSourceGroup() { - this.isSource() and - this.getConfiguration().sourceGrouping(this.getNodeEx().asNode(), result) + not singleConfiguration() and + getConfig(state1).isAdditionalFlowStep(node1, node2) and + state2 = state1 } - predicate isFlowSource() { - this.isSource() and not exists(this.getSourceGroup()) - or - this instanceof PathNodeSourceGroup + predicate allowImplicitRead(Node node, ContentSet c) { + any(Configuration config).allowImplicitRead(node, c) } - predicate isFlowSink() { - this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or - this instanceof PathNodeSinkGroup - } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } - private string ppAp() { - this instanceof PathNodeSink and result = "" - or - exists(string s | s = this.(PathNodeMid).getAp().toString() | - if s = "" then result = "" else result = " " + s - ) - } + FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } - private string ppCtx() { - this instanceof PathNodeSink and result = "" - or - result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + predicate sourceGrouping(Node source, string sourceGroup) { + any(Configuration config).sourceGrouping(source, sourceGroup) } - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + predicate sinkGrouping(Node sink, string sinkGroup) { + any(Configuration config).sinkGrouping(sink, sinkGroup) } -} - -/** Holds if `n` can reach a sink. */ -private predicate directReach(PathNodeImpl n) { - n instanceof PathNodeSink or - n instanceof PathNodeSinkGroup or - directReach(n.getANonHiddenSuccessor()) -} - -/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */ -private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) } -/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) { - n1.getANonHiddenSuccessor() = n2 and directReach(n2) + predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() } } -private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2) +private import Impl as I +import I /** * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. */ -class PathNode instanceof PathNodeImpl { - PathNode() { reach(this) } - +class PathNode instanceof I::PathNode { /** Gets a textual representation of this element. */ final string toString() { result = super.toString() } @@ -3406,1548 +358,39 @@ class PathNode instanceof PathNodeImpl { } /** Gets the underlying `Node`. */ - final Node getNode() { super.getNodeEx().projectToNode() = result } + final Node getNode() { result = super.getNode() } /** Gets the `FlowState` of this node. */ - final FlowState getState() { result = super.getState() } + final FlowState getState() { result = getState(super.getState()) } /** Gets the associated configuration. */ - final Configuration getConfiguration() { result = super.getConfiguration() } + final Configuration getConfiguration() { result = getConfig(super.getState()) } /** Gets a successor of this node, if any. */ - final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() } + final PathNode getASuccessor() { result = super.getASuccessor() } /** Holds if this node is a source. */ final predicate isSource() { super.isSource() } /** Holds if this node is a grouping of source nodes. */ - final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group, _) } + final predicate isSourceGroup(string group) { super.isSourceGroup(group) } /** Holds if this node is a grouping of sink nodes. */ - final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group, _) } + final predicate isSinkGroup(string group) { super.isSinkGroup(group) } } -/** - * Provides the query predicates needed to include a graph in a path-problem query. - */ -module PathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b } - - /** Holds if `n` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode n, string key, string val) { - key = "semmle.label" and val = n.toString() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) { - Subpaths::subpaths(arg, par, ret, out) - } -} - -/** - * An intermediate flow graph node. This is a triple consisting of a `Node`, - * a `CallContext`, and a `Configuration`. - */ -private class PathNodeMid extends PathNodeImpl, TPathNodeMid { - NodeEx node; - FlowState state; - CallContext cc; - SummaryCtx sc; - AccessPath ap; - Configuration config; - - PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - SummaryCtx getSummaryCtx() { result = sc } - - AccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - private PathNodeMid getSuccMid() { - pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx(), result.getAp()) and - result.getConfiguration() = unbindConf(this.getConfiguration()) - } - - override PathNodeImpl getASuccessorImpl() { - // an intermediate step to another intermediate node - result = this.getSuccMid() - or - // a final step to a sink - result = this.getSuccMid().projectToSink() - } - - override predicate isSource() { - sourceNode(node, state, config) and - ( - if hasSourceCallCtx(config) - then cc instanceof CallContextSomeCall - else cc instanceof CallContextAny - ) and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - } - - predicate isAtSink() { - sinkNode(node, state, config) and - ap instanceof AccessPathNil and - if hasSinkCallCtx(config) - then - // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall` - // is exactly what we need to check. This also implies - // `sc instanceof SummaryCtxNone`. - // For `FeatureEqualSourceSinkCallContext` the initial call context was - // set to `CallContextSomeCall` and jumps are disallowed, so - // `cc instanceof CallContextNoCall` never holds. On the other hand, - // in this case there's never any need to enter a call except to identify - // a summary, so the condition in `pathIntoCallable` enforces this, which - // means that `sc instanceof SummaryCtxNone` holds if and only if we are - // in the call context of the source. - sc instanceof SummaryCtxNone or - cc instanceof CallContextNoCall - else any() - } - - PathNodeSink projectToSink() { - this.isAtSink() and - result.getNodeEx() = node and - result.getState() = state and - result.getConfiguration() = unbindConf(config) - } -} - -/** - * A flow graph node corresponding to a sink. This is disjoint from the - * intermediate nodes in order to uniquely correspond to a given sink by - * excluding the `CallContext`. - */ -private class PathNodeSink extends PathNodeImpl, TPathNodeSink { - NodeEx node; - FlowState state; - Configuration config; - - PathNodeSink() { this = TPathNodeSink(node, state, config) } - - override NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result = TPathNodeSinkGroup(this.getSinkGroup(), config) - } - - override predicate isSource() { sourceNode(node, state, config) } - - string getSinkGroup() { config.sinkGrouping(node.asNode(), result) } -} - -private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup { - string sourceGroup; - Configuration config; - - PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { - result.getSourceGroup() = sourceGroup and - result.getConfiguration() = config - } - - override predicate isSource() { none() } - - override string toString() { result = sourceGroup } - - override 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 - } -} - -private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup { - string sinkGroup; - Configuration config; - - PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup, config) } - - override NodeEx getNodeEx() { none() } - - override FlowState getState() { none() } - - override Configuration getConfiguration() { result = config } - - override PathNodeImpl getASuccessorImpl() { none() } - - override predicate isSource() { none() } - - override string toString() { result = sinkGroup } - - override 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 - } -} - -private predicate pathNode( - PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, - Configuration conf, LocalCallContext localCC -) { - midnode = mid.getNodeEx() and - state = mid.getState() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - midnode.getEnclosingCallable()) and - ap = mid.getAp() -} - -/** - * Holds if data may flow from `mid` to `node`. The last step in or out of - * a callable is recorded by `cc`. - */ -pragma[assume_small_delta] -pragma[nomagic] -private predicate pathStep( - PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap -) { - exists(NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC | - pathNode(mid, midnode, state0, cc, sc, ap, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, true, _, conf, localCC) - ) - or - exists( - AccessPath ap0, NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC - | - pathNode(mid, midnode, state0, cc, sc, ap0, conf, localCC) and - localFlowBigStep(midnode, state0, node, state, false, ap.(AccessPathNil).getType(), conf, - localCC) and - ap0 instanceof AccessPathNil - ) - or - jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - ap = mid.getAp() - or - additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - state = mid.getState() and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, mid.getConfiguration()) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - pathIntoCallable(mid, node, state, _, cc, sc, _, _) and ap = mid.getAp() - or - pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone - or - pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx() -} - -pragma[nomagic] -private predicate pathReadStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - tc = ap0.getHead() and - Stage5::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -pragma[nomagic] -private predicate pathStoreStep( - PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - Stage5::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and - state = mid.getState() and - cc = mid.getCallContext() -} - -private predicate pathOutOfCallable0( - PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa, - Configuration config -) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - apa = mid.getAp().getApprox() and - config = mid.getConfiguration() -} - -pragma[nomagic] -private predicate pathOutOfCallable1( - PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPathApprox apa, Configuration config -) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - pathOutOfCallable0(mid, pos, state, innercc, apa, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() +private predicate hasFlow(Node source, Node sink, Configuration config) { + exists(PathNode source0, PathNode sink0 | + hasFlowPath(source0, sink0, config) and + source0.getNode() = source and + sink0.getNode() = sink ) } -pragma[noinline] -private NodeEx getAnOutNodeFlow( - ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config -) { - result.asNode() = kind.getAnOutNode(call) and - Stage5::revFlow(result, _, apa, config) +private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) { + hasFlowPath(source, sink) and source.getConfiguration() = config } -/** - * Holds if data may flow from `mid` to `out`. The last step of this path - * is a return from a callable and is recorded by `cc`, if needed. - */ -pragma[noinline] -private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, FlowState state, CallContext cc) { - exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | - pathOutOfCallable1(mid, call, kind, state, cc, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} +private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) } -/** - * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. - */ -pragma[noinline] -private predicate pathIntoArg( - PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(ArgNodeEx arg, ArgumentPosition apos | - pathNode(mid, arg, state, cc, _, ap, config, _) and - arg.asNode().(ArgNode).argumentOf(call, apos) and - apa = ap.getApprox() and - parameterMatch(ppos, apos) - ) -} - -pragma[nomagic] -private predicate parameterCand( - DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage5::revFlow(p, _, apa, config) and - p.isParameterOf(callable, pos) - ) -} - -pragma[nomagic] -private predicate pathIntoCallable0( - PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, AccessPath ap, Configuration config -) { - exists(AccessPathApprox apa | - pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap, - pragma[only_bind_into](apa), pragma[only_bind_into](config)) and - callable = resolveCall(call, outercc) and - parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa), - pragma[only_bind_into](config)) - ) -} - -/** - * Holds if data may flow from `mid` to `p` through `call`. The contexts - * before and after entering the callable are `outercc` and `innercc`, - * respectively. - */ -pragma[nomagic] -private predicate pathIntoCallable( - PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc, - SummaryCtx sc, DataFlowCall call, Configuration config -) { - exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap | - pathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - ( - sc = TSummaryCtxSome(p, state, ap) - or - not exists(TSummaryCtxSome(p, state, ap)) and - sc = TSummaryCtxNone() and - // When the call contexts of source and sink needs to match then there's - // never any reason to enter a callable except to find a summary. See also - // the comment in `PathNodeMid::isAtSink`. - not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext - ) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) -} - -/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ -pragma[nomagic] -private predicate paramFlowsThrough( - ReturnKindExt kind, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, - AccessPathApprox apa, Configuration config -) { - exists(RetNodeEx ret | - pathNode(_, ret, state, cc, sc, ap, config, _) and - kind = ret.getKind() and - apa = ap.getApprox() and - parameterFlowThroughAllowed(sc.getParamNode(), kind) - ) -} - -pragma[nomagic] -private predicate pathThroughCallable0( - DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc, - AccessPath ap, AccessPathApprox apa, Configuration config -) { - exists(CallContext innercc, SummaryCtx sc | - pathIntoCallable(mid, _, _, cc, innercc, sc, call, config) and - paramFlowsThrough(kind, state, innercc, sc, ap, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` through a callable to the node `out`. - * The context `cc` is restored to its value prior to entering the callable. - */ -pragma[noinline] -private predicate pathThroughCallable( - PathNodeMid mid, NodeEx out, FlowState state, CallContext cc, AccessPath ap -) { - exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config | - pathThroughCallable0(call, mid, kind, state, cc, ap, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -private module Subpaths { - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths01( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - exists(Configuration config | - pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and - pathIntoCallable(arg, par, _, _, innercc, sc, _, config) and - paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc, - pragma[only_bind_into](apout), _, unbindConf(config)) and - not arg.isHidden() - ) - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `sout`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths02( - PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, FlowState sout, AccessPath apout - ) { - subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and - out.asNode() = kind.getAnOutNode(_) - } - - pragma[nomagic] - private Configuration getPathNodeConf(PathNodeImpl n) { result = n.getConfiguration() } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple. - */ - pragma[nomagic] - private predicate subpaths03( - PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, AccessPath apout - ) { - exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | - subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and - pathNode(ret, retnode, sout, innercc, sc, apout, unbindConf(getPathNodeConf(arg)), _) and - kind = retnode.getKind() - ) - } - - private PathNodeImpl localStepToHidden(PathNodeImpl n) { - n.getASuccessorImpl() = result and - result.isHidden() and - exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() | - localFlowBigStep(n1, _, n2, _, _, _, _, _) or - store(n1, _, n2, _, _) or - readSet(n1, _, n2, _) - ) - } - - pragma[nomagic] - private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) { - succ = pred.getANonHiddenSuccessor() and - succNode = succ.getNodeEx() - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) { - exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 | - pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and - subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and - hasSuccessor(pragma[only_bind_into](arg), par, p) and - not ret.isHidden() and - pathNode(out0, o, sout, _, _, apout, _, _) - | - out = out0 or out = out0.projectToSink() - ) - } - - /** - * Holds if `n` can reach a return node in a summarized subpath that can reach a sink. - */ - predicate retReach(PathNodeImpl n) { - exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out)) - or - exists(PathNodeImpl mid | - retReach(mid) and - n.getANonHiddenSuccessor() = mid and - not subpaths(_, mid, _, _) - ) - } -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -private predicate hasFlowPath( - PathNodeImpl flowsource, PathNodeImpl flowsink, Configuration configuration -) { - flowsource.isFlowSource() and - flowsource.getConfiguration() = configuration and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.isFlowSink() -} - -private predicate flowsTo( - PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink, - Configuration configuration -) { - flowsource.isSource() and - flowsource.getConfiguration() = configuration and - flowsource.getNodeEx().asNode() = source and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.getNodeEx().asNode() = sink -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -predicate flowsTo(Node source, Node sink, Configuration configuration) { - flowsTo(_, _, source, sink, configuration) -} - -private predicate finalStats( - boolean fwd, int nodes, int fields, int conscand, int states, int tuples -) { - fwd = true and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and - tuples = count(PathNodeImpl pn) - or - fwd = false and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and - states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and - tuples = count(PathNode pn) -} - -/** - * INTERNAL: Only for debugging. - * - * Calculates per-stage metrics for data flow. - */ -predicate stageStats( - int n, string stage, int nodes, int fields, int conscand, int states, int tuples, - Configuration config -) { - stage = "1 Fwd" and - n = 10 and - Stage1::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "1 Rev" and - n = 15 and - Stage1::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Fwd" and - n = 20 and - Stage2::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "2 Rev" and - n = 25 and - Stage2::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Fwd" and - n = 30 and - Stage3::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "3 Rev" and - n = 35 and - Stage3::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Fwd" and - n = 40 and - Stage4::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "4 Rev" and - n = 45 and - Stage4::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Fwd" and - n = 50 and - Stage5::stats(true, nodes, fields, conscand, states, tuples, config) - or - stage = "5 Rev" and - n = 55 and - Stage5::stats(false, nodes, fields, conscand, states, tuples, config) - or - stage = "6 Fwd" and n = 60 and finalStats(true, nodes, fields, conscand, states, tuples) - or - stage = "6 Rev" and n = 65 and finalStats(false, nodes, fields, conscand, states, tuples) -} - -private module FlowExploration { - private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { - exists(NodeEx node1, NodeEx node2 | - jumpStep(node1, node2, config) - or - additionalJumpStep(node1, node2, config) - or - additionalJumpStateStep(node1, _, node2, _, config) - or - // flow into callable - viableParamArgEx(_, node2, node1) - or - // flow out of a callable - viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) - | - c1 = node1.getEnclosingCallable() and - c2 = node2.getEnclosingCallable() and - c1 != c2 - ) - } - - private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSource(n) or config.isSource(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSrc(mid, config) and callableStep(mid, c, config) - ) - } - - private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSink(n) or config.isSink(n, _) | c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSink(mid, config) and callableStep(c, mid, config) - ) - } - - private newtype TCallableExt = - TCallable(DataFlowCallable c, Configuration config) { - interestingCallableSrc(c, config) or - interestingCallableSink(c, config) - } or - TCallableSrc() or - TCallableSink() - - private predicate callableExtSrc(TCallableSrc src) { any() } - - private predicate callableExtSink(TCallableSink sink) { any() } - - private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { - exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | - callableStep(c1, c2, config) and - ce1 = TCallable(c1, pragma[only_bind_into](config)) and - ce2 = TCallable(c2, pragma[only_bind_into](config)) - ) - or - exists(Node n, Configuration config | - ce1 = TCallableSrc() and - (config.isSource(n) or config.isSource(n, _)) and - ce2 = TCallable(getNodeEnclosingCallable(n), config) - ) - or - exists(Node n, Configuration config | - ce2 = TCallableSink() and - (config.isSink(n) or config.isSink(n, _)) and - ce1 = TCallable(getNodeEnclosingCallable(n), config) - ) - } - - private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { - callableExtStepFwd(ce2, ce1) - } - - private int distSrcExt(TCallableExt c) = - shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) - - private int distSinkExt(TCallableExt c) = - shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) - - private int distSrc(DataFlowCallable c, Configuration config) { - result = distSrcExt(TCallable(c, config)) - 1 - } - - private int distSink(DataFlowCallable c, Configuration config) { - result = distSinkExt(TCallable(c, config)) - 1 - } - - private newtype TPartialAccessPath = - TPartialNil(DataFlowType t) or - TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first - * element of the list and its length are tracked. If data flows from a source to - * a given node with a given `AccessPath`, this indicates the sequence of - * dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ - private class PartialAccessPath extends TPartialAccessPath { - abstract string toString(); - - TypedContent getHead() { this = TPartialCons(result, _) } - - int len() { - this = TPartialNil(_) and result = 0 - or - this = TPartialCons(_, result) - } - - DataFlowType getType() { - this = TPartialNil(result) - or - exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) - } - } - - private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { - override string toString() { - exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) - } - } - - private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { - override string toString() { - exists(TypedContent tc, int len | this = TPartialCons(tc, len) | - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private newtype TRevPartialAccessPath = - TRevPartialNil() or - TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `Content`s, but only the first - * element of the list and its length are tracked. - */ - private class RevPartialAccessPath extends TRevPartialAccessPath { - abstract string toString(); - - Content getHead() { this = TRevPartialCons(result, _) } - - int len() { - this = TRevPartialNil() and result = 0 - or - this = TRevPartialCons(_, result) - } - } - - private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { - override string toString() { result = "" } - } - - private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { - override string toString() { - exists(Content c, int len | this = TRevPartialCons(c, len) | - if len = 1 - then result = "[" + c.toString() + "]" - else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private predicate relevantState(FlowState state) { - sourceNode(_, state, _) or - sinkNode(_, state, _) or - additionalLocalStateStep(_, state, _, _, _) or - additionalLocalStateStep(_, _, _, state, _) or - additionalJumpStateStep(_, state, _, _, _) or - additionalJumpStateStep(_, _, _, state, _) - } - - private newtype TSummaryCtx1 = - TSummaryCtx1None() or - TSummaryCtx1Param(ParamNodeEx p) - - private newtype TSummaryCtx2 = - TSummaryCtx2None() or - TSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TSummaryCtx3 = - TSummaryCtx3None() or - TSummaryCtx3Some(PartialAccessPath ap) - - private newtype TRevSummaryCtx1 = - TRevSummaryCtx1None() or - TRevSummaryCtx1Some(ReturnPosition pos) - - private newtype TRevSummaryCtx2 = - TRevSummaryCtx2None() or - TRevSummaryCtx2Some(FlowState s) { relevantState(s) } - - private newtype TRevSummaryCtx3 = - TRevSummaryCtx3None() or - TRevSummaryCtx3Some(RevPartialAccessPath ap) - - private newtype TPartialPathNode = - TPartialPathNodeFwd( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = TPartialNil(node.getDataFlowType()) and - exists(config.explorationLimit()) - or - partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap, config) and - distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() - } or - TPartialPathNodeRev( - NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3, - RevPartialAccessPath ap, Configuration config - ) { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() and - exists(config.explorationLimit()) - or - revPartialPathStep(_, node, state, sc1, sc2, sc3, ap, config) and - not clearsContentEx(node, ap.getHead()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead()) - ) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() - } - - pragma[nomagic] - private predicate partialPathNodeMk0( - NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - partialPathStep(_, node, state, cc, sc1, sc2, sc3, ap, config) and - not fullBarrier(node, config) and - not stateBarrier(node, state, config) and - not clearsContentEx(node, ap.getHead().getContent()) and - ( - notExpectsContent(node) or - expectsContentEx(node, ap.getHead().getContent()) - ) and - if node.asNode() instanceof CastingNode - then compatibleTypes(node.getDataFlowType(), ap.getType()) - else any() - } - - /** - * A `Node` augmented with a call context, an access path, and a configuration. - */ - class PartialPathNode extends TPartialPathNode { - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() - } - - /** - * 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 - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - /** Gets the underlying `Node`. */ - final Node getNode() { this.getNodeEx().projectToNode() = result } - - FlowState getState() { none() } - - private NodeEx getNodeEx() { - result = this.(PartialPathNodeFwd).getNodeEx() or - result = this.(PartialPathNodeRev).getNodeEx() - } - - /** Gets the associated configuration. */ - Configuration getConfiguration() { none() } - - /** Gets a successor of this node, if any. */ - PartialPathNode getASuccessor() { none() } - - /** - * Gets the approximate distance to the nearest source measured in number - * of interprocedural steps. - */ - int getSourceDistance() { - result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - /** - * Gets the approximate distance to the nearest sink measured in number - * of interprocedural steps. - */ - int getSinkDistance() { - result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - private string ppAp() { - exists(string s | - s = this.(PartialPathNodeFwd).getAp().toString() or - s = this.(PartialPathNodeRev).getAp().toString() - | - if s = "" then result = "" else result = " " + s - ) - } - - private string ppCtx() { - result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" - } - - /** Holds if this is a source in a forward-flow path. */ - predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } - - /** Holds if this is a sink in a reverse-flow path. */ - predicate isRevSink() { this.(PartialPathNodeRev).isSink() } - } - - /** - * Provides the query predicates needed to include a graph in a path-problem query. - */ - module PartialPathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } - } - - private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { - NodeEx node; - FlowState state; - CallContext cc; - TSummaryCtx1 sc1; - TSummaryCtx2 sc2; - TSummaryCtx3 sc3; - PartialAccessPath ap; - Configuration config; - - PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - CallContext getCallContext() { result = cc } - - TSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TSummaryCtx3 getSummaryCtx3() { result = sc3 } - - PartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeFwd getASuccessor() { - partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(), - result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp(), - result.getConfiguration()) - } - - predicate isSource() { - sourceNode(node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap instanceof TPartialNil - } - } - - private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { - NodeEx node; - FlowState state; - TRevSummaryCtx1 sc1; - TRevSummaryCtx2 sc2; - TRevSummaryCtx3 sc3; - RevPartialAccessPath ap; - Configuration config; - - PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap, config) } - - NodeEx getNodeEx() { result = node } - - override FlowState getState() { result = state } - - TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } - - TRevSummaryCtx3 getSummaryCtx3() { result = sc3 } - - RevPartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeRev getASuccessor() { - revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(), - this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp(), this.getConfiguration()) - } - - predicate isSink() { - sinkNode(node, state, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = TRevPartialNil() - } - } - - private predicate partialPathStep( - PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, - TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and - ( - localFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - ) - or - jumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(mid.getNodeEx(), node, config) and - state = mid.getState() and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - partialPathStoreStep(mid, _, _, node, ap) and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(PartialAccessPath ap0, TypedContent tc | - partialPathReadStep(mid, ap0, tc, node, cc, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsFwd(ap, tc, ap0, config) - ) - or - partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap, config) - or - partialPathOutOfCallable(mid, node, state, cc, ap, config) and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - sc3 = TSummaryCtx3None() - or - partialPathThroughCallable(mid, node, state, cc, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - bindingset[result, i] - private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) } - - pragma[inline] - private predicate partialPathStoreStep( - PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, - PartialAccessPath ap2 - ) { - exists(NodeEx midNode, DataFlowType contentType | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - store(midNode, tc, node, contentType, mid.getConfiguration()) and - ap2.getHead() = tc and - ap2.len() = unbindInt(ap1.len() + 1) and - compatibleTypes(ap1.getType(), contentType) - ) - } - - pragma[nomagic] - private predicate apConsFwd( - PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeFwd mid | - partialPathStoreStep(mid, ap1, tc, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate partialPathReadStep( - PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc, - Configuration config - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and - ap.getHead() = tc and - pragma[only_bind_into](config) = mid.getConfiguration() and - cc = mid.getCallContext() - ) - } - - private predicate partialPathOutOfCallable0( - PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc, - PartialAccessPath ap, Configuration config - ) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - state = mid.getState() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - ap = mid.getAp() and - config = mid.getConfiguration() - } - - pragma[nomagic] - private predicate partialPathOutOfCallable1( - PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - partialPathOutOfCallable0(mid, pos, state, innercc, ap, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) - } - - private predicate partialPathOutOfCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(ReturnKindExt kind, DataFlowCall call | - partialPathOutOfCallable1(mid, call, kind, state, cc, ap, config) - | - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[noinline] - private predicate partialPathIntoArg( - PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ArgNode arg, ArgumentPosition apos | - arg = mid.getNodeEx().asNode() and - state = mid.getState() and - cc = mid.getCallContext() and - arg.argumentOf(call, apos) and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate partialPathIntoCallable0( - PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state, - CallContext outercc, DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - partialPathIntoArg(mid, pos, state, outercc, call, ap, config) and - callable = resolveCall(call, outercc) - } - - private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc, - CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - exists(ParameterPosition pos, DataFlowCallable callable | - partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and - p.isParameterOf(callable, pos) and - sc1 = TSummaryCtx1Param(p) and - sc2 = TSummaryCtx2Some(state) and - sc3 = TSummaryCtx3Some(ap) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) - } - - pragma[nomagic] - private predicate paramFlowsThroughInPartialPath( - ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeFwd mid, RetNodeEx ret | - mid.getNodeEx() = ret and - kind = ret.getKind() and - state = mid.getState() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() and - ap = mid.getAp() - ) - } - - pragma[noinline] - private predicate partialPathThroughCallable0( - DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 | - partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _, config) and - paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap, config) - ) - } - - private predicate partialPathThroughCallable( - PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ReturnKindExt kind | - partialPathThroughCallable0(call, mid, kind, state, cc, ap, config) and - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[nomagic] - private predicate revPartialPathStep( - PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, - TRevSummaryCtx3 sc3, RevPartialAccessPath ap, Configuration config - ) { - localFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - jumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(node, mid.getNodeEx(), config) and - state = mid.getState() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - revPartialPathReadStep(mid, _, _, node, ap) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - config = mid.getConfiguration() - or - exists(RevPartialAccessPath ap0, Content c | - revPartialPathStoreStep(mid, ap0, c, node, config) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - apConsRev(ap, c, ap0, config) - ) - or - exists(ParamNodeEx p | - mid.getNodeEx() = p and - viableParamArgEx(_, p, node) and - state = mid.getState() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - sc3 = TRevSummaryCtx3None() and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - or - exists(ReturnPosition pos | - revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap, config) and - pos = getReturnPosition(node.asNode()) - ) - or - revPartialPathThroughCallable(mid, node, state, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() - } - - pragma[inline] - private predicate revPartialPathReadStep( - PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, - RevPartialAccessPath ap2 - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - read(node, c, midNode, mid.getConfiguration()) and - ap2.getHead() = c and - ap2.len() = unbindInt(ap1.len() + 1) - ) - } - - pragma[nomagic] - private predicate apConsRev( - RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeRev mid | - revPartialPathReadStep(mid, ap1, c, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathStoreStep( - PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config - ) { - exists(NodeEx midNode, TypedContent tc | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - store(node, tc, midNode, _, config) and - ap.getHead() = c and - config = mid.getConfiguration() and - tc.getContent() = c - ) - } - - pragma[nomagic] - private predicate revPartialPathIntoReturn( - PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1, - TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap, - Configuration config - ) { - exists(NodeEx out | - mid.getNodeEx() = out and - mid.getState() = state and - viableReturnPosOutEx(call, pos, out) and - sc1 = TRevSummaryCtx1Some(pos) and - sc2 = TRevSummaryCtx2Some(state) and - sc3 = TRevSummaryCtx3Some(ap) and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathFlowsThrough( - ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, - TRevSummaryCtx3Some sc3, RevPartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos | - mid.getNodeEx() = p and - mid.getState() = state and - p.getPosition() = ppos and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc3 = mid.getSummaryCtx3() and - ap = mid.getAp() and - config = mid.getConfiguration() and - parameterMatch(ppos, apos) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable0( - DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state, - RevPartialAccessPath ap, Configuration config - ) { - exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 | - revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _, config) and - revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap, config) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap, - Configuration config - ) { - exists(DataFlowCall call, ArgumentPosition pos | - revPartialPathThroughCallable0(call, mid, pos, state, ap, config) and - node.asNode().(ArgNode).argumentOf(call, pos) - ) - } -} - -import FlowExploration - -private predicate partialFlow( - PartialPathNode source, PartialPathNode node, Configuration configuration -) { - source.getConfiguration() = configuration and - source.isFwdSource() and - node = source.getASuccessor+() -} - -private predicate revPartialFlow( - PartialPathNode node, PartialPathNode sink, Configuration configuration -) { - sink.getConfiguration() = configuration and - sink.isRevSink() and - node.getASuccessor+() = sink -} +predicate flowsTo = hasFlow/3; diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll b/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll index 85ffeb62e5f..468ed73e81a 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll @@ -248,7 +248,9 @@ module Public { /** * Holds if all the summaries that apply to `this` are auto generated and not manually created. */ - final predicate isAutoGenerated() { this.hasProvenance("generated") and not this.isManual() } + final predicate isAutoGenerated() { + this.hasProvenance(["generated", "ai-generated"]) and not this.isManual() + } /** * Holds if there exists a manual summary that applies to `this`. @@ -268,7 +270,7 @@ module Public { /** * Holds if the neutral is auto generated. */ - predicate isAutoGenerated() { neutralElement(this, "generated") } + predicate isAutoGenerated() { neutralElement(this, ["generated", "ai-generated"]) } /** * Holds if there exists a manual neutral that applies to `this`. @@ -1202,11 +1204,11 @@ module Private { } private string renderProvenance(SummarizedCallable c) { - if c.isAutoGenerated() then result = "generated" else result = "manual" + if c.isManual() then result = "manual" else c.hasProvenance(result) } private string renderProvenanceNeutral(NeutralCallable c) { - if c.isAutoGenerated() then result = "generated" else result = "manual" + if c.isManual() then result = "manual" else c.hasProvenance(result) } /** diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/tainttracking1/TaintTracking.qll b/java/ql/lib/semmle/code/java/dataflow/internal/tainttracking1/TaintTracking.qll new file mode 100644 index 00000000000..7333264298e --- /dev/null +++ b/java/ql/lib/semmle/code/java/dataflow/internal/tainttracking1/TaintTracking.qll @@ -0,0 +1,63 @@ +/** + * Provides classes for performing local (intra-procedural) and + * global (inter-procedural) taint-tracking analyses. + */ + +import TaintTrackingParameter::Public +private import TaintTrackingParameter::Private + +private module AddTaintDefaults implements +DataFlowInternal::FullStateConfigSig { + import Config + + predicate isBarrier(DataFlow::Node node) { + Config::isBarrier(node) or defaultTaintSanitizer(node) + } + + predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + Config::isAdditionalFlowStep(node1, node2) or + defaultAdditionalTaintStep(node1, node2) + } + + predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) { + Config::allowImplicitRead(node, c) + or + ( + Config::isSink(node, _) or + Config::isAdditionalFlowStep(node, _) or + Config::isAdditionalFlowStep(node, _, _, _) + ) and + defaultImplicitTaintRead(node, c) + } +} + +/** + * Constructs a standard taint tracking computation. + */ +module Make implements DataFlow::DataFlowSig { + private module Config0 implements DataFlowInternal::FullStateConfigSig { + import DataFlowInternal::DefaultState + import Config + } + + private module C implements DataFlowInternal::FullStateConfigSig { + import AddTaintDefaults + } + + import DataFlowInternal::Impl +} + +/** + * Constructs a taint tracking computation using flow state. + */ +module MakeWithState implements DataFlow::DataFlowSig { + private module Config0 implements DataFlowInternal::FullStateConfigSig { + import Config + } + + private module C implements DataFlowInternal::FullStateConfigSig { + import AddTaintDefaults + } + + import DataFlowInternal::Impl +} diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/tainttracking1/TaintTrackingParameter.qll b/java/ql/lib/semmle/code/java/dataflow/internal/tainttracking1/TaintTrackingParameter.qll index d2acc1130e5..2608adffda3 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/tainttracking1/TaintTrackingParameter.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/tainttracking1/TaintTrackingParameter.qll @@ -2,4 +2,5 @@ import semmle.code.java.dataflow.internal.TaintTrackingUtil as Public module Private { import semmle.code.java.dataflow.DataFlow::DataFlow as DataFlow + import semmle.code.java.dataflow.internal.DataFlowImpl as DataFlowInternal } diff --git a/java/ql/lib/semmle/code/java/security/SensitiveApi.qll b/java/ql/lib/semmle/code/java/security/SensitiveApi.qll index 9936ab202a3..1baff16a100 100644 --- a/java/ql/lib/semmle/code/java/security/SensitiveApi.qll +++ b/java/ql/lib/semmle/code/java/security/SensitiveApi.qll @@ -490,5 +490,11 @@ private predicate otherApiCallableCredentialParam(string s) { "com.microsoft.sqlserver.jdbc.SQLServerDataSource;setPassword(String);0", "com.microsoft.sqlserver.jdbc.SQLServerDataSource;getConnection(String, String);0", "com.microsoft.sqlserver.jdbc.SQLServerDataSource;getConnection(String, String);1", + "com.auth0.jwt.algorithms.Algorithm;HMAC256(String);0", + "com.auth0.jwt.algorithms.Algorithm;HMAC256(byte[]);0", + "com.auth0.jwt.algorithms.Algorithm;HMAC384(String);0", + "com.auth0.jwt.algorithms.Algorithm;HMAC384(byte[]);0", + "com.auth0.jwt.algorithms.Algorithm;HMAC512(String);0", + "com.auth0.jwt.algorithms.Algorithm;HMAC512(byte[]);0" ] } diff --git a/java/ql/src/Metrics/Summaries/GeneratedVsManualCoverage.ql b/java/ql/src/Metrics/Summaries/GeneratedVsManualCoverage.ql index a37f2c005ac..a689e9a7fcb 100644 --- a/java/ql/src/Metrics/Summaries/GeneratedVsManualCoverage.ql +++ b/java/ql/src/Metrics/Summaries/GeneratedVsManualCoverage.ql @@ -28,7 +28,7 @@ private int getNumMadModeledApis(string package, string provenance) { or sc.isManual() and ( - if sc.hasProvenance("generated") + if sc.hasProvenance(["generated", "ai-generated"]) then // "both" provenance = "both" diff --git a/java/ql/src/experimental/Security/CWE/CWE-321/HardcodedJwtKey.java b/java/ql/src/experimental/Security/CWE/CWE-321/HardcodedJwtKey.java deleted file mode 100644 index 69712794689..00000000000 --- a/java/ql/src/experimental/Security/CWE/CWE-321/HardcodedJwtKey.java +++ /dev/null @@ -1,26 +0,0 @@ -// BAD: Get secret from hardcoded string then sign a JWT token -Algorithm algorithm = Algorithm.HMAC256("hardcoded_secret"); -JWT.create() - .withClaim("username", username) - .sign(algorithm); -} - -// BAD: Get secret from hardcoded string then verify a JWT token -JWTVerifier verifier = JWT.require(Algorithm.HMAC256("hardcoded_secret")) - .withIssuer(ISSUER) - .build(); -verifier.verify(token); - -// GOOD: Get secret from system configuration then sign a token -String tokenSecret = System.getenv("SECRET_KEY"); -Algorithm algorithm = Algorithm.HMAC256(tokenSecret); -JWT.create() - .withClaim("username", username) - .sign(algorithm); - } - -// GOOD: Get secret from environment variable then verify a JWT token -JWTVerifier verifier = JWT.require(Algorithm.HMAC256(System.getenv("SECRET_KEY"))) - .withIssuer(ISSUER) - .build(); -verifier.verify(token); diff --git a/java/ql/src/experimental/Security/CWE/CWE-321/HardcodedJwtKey.qhelp b/java/ql/src/experimental/Security/CWE/CWE-321/HardcodedJwtKey.qhelp deleted file mode 100644 index e6a25a3b96c..00000000000 --- a/java/ql/src/experimental/Security/CWE/CWE-321/HardcodedJwtKey.qhelp +++ /dev/null @@ -1,46 +0,0 @@ - - - -

    - JWT (JSON Web Token) is an open standard (RFC 7519) that defines a way to provide information - within a JSON object between two parties. JWT is widely used for sharing security information - between two parties in web applications. Each JWT contains encoded JSON objects, including a - set of claims. JWTs are signed using a cryptographic algorithm to ensure that the claims cannot - be altered after the token is issued. -

    -

    - The most basic mistake is using hardcoded secrets for JWT generation/verification. This allows - an attacker to forge the token if the source code (and JWT secret in it) is publicly exposed or - leaked, which leads to authentication bypass or privilege escalation. -

    -
    - - -

    - Generating a cryptographically secure secret key during application initialization and using this - generated key for JWT signing/verification requests can prevent this vulnerability. Or safely store - the secret key in a key vault that cannot be leaked in source code. -

    -
    - - -

    - The following examples show the bad case and the good case respectively. The bad - methods show a hardcoded secret key is used to sign and verify JWT tokens. In the good - method, the secret key is loaded from a system environment during application initialization. -

    - -
    - - -
  • - Semgrep Blog: - Hardcoded secrets, unverified tokens, and other common JWT mistakes -
  • -
  • - CVE-2022-24860: - Databasir 1.01 has Use of Hard-coded Cryptographic Key vulnerability. -
  • -
    - -
    diff --git a/java/ql/src/experimental/Security/CWE/CWE-321/HardcodedJwtKey.ql b/java/ql/src/experimental/Security/CWE/CWE-321/HardcodedJwtKey.ql deleted file mode 100644 index 521a355bdab..00000000000 --- a/java/ql/src/experimental/Security/CWE/CWE-321/HardcodedJwtKey.ql +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @name Use of a hardcoded key for signing JWT - * @description Using a hardcoded key for signing JWT can allow an attacker to compromise security. - * @kind path-problem - * @problem.severity error - * @id java/hardcoded-jwt-key - * @tags security - * experimental - * external/cwe/cwe-321 - */ - -import java -import HardcodedJwtKey -import semmle.code.java.dataflow.TaintTracking -import DataFlow::PathGraph - -from DataFlow::PathNode source, DataFlow::PathNode sink, HardcodedJwtKeyConfiguration cfg -where cfg.hasFlowPath(source, sink) -select sink.getNode(), source, sink, "$@ is used to sign a JWT token.", source.getNode(), - "Hardcoded String" diff --git a/java/ql/src/experimental/Security/CWE/CWE-321/HardcodedJwtKey.qll b/java/ql/src/experimental/Security/CWE/CWE-321/HardcodedJwtKey.qll deleted file mode 100644 index 09db11dd40b..00000000000 --- a/java/ql/src/experimental/Security/CWE/CWE-321/HardcodedJwtKey.qll +++ /dev/null @@ -1,131 +0,0 @@ -/** - * Provides sources and sinks for detecting JWT token signing vulnerabilities. - */ - -import java -private import semmle.code.java.dataflow.ExternalFlow -private import semmle.code.java.dataflow.FlowSources - -private class ActivateModels extends ActiveExperimentalModels { - ActivateModels() { this = "hardcoded-jwt-key" } -} - -/** The class `com.auth0.jwt.JWT`. */ -class Jwt extends RefType { - Jwt() { this.hasQualifiedName("com.auth0.jwt", "JWT") } -} - -/** The class `com.auth0.jwt.JWTCreator.Builder`. */ -class JwtBuilder extends RefType { - JwtBuilder() { this.hasQualifiedName("com.auth0.jwt", "JWTCreator$Builder") } -} - -/** The class `com.auth0.jwt.algorithms.Algorithm`. */ -class JwtAlgorithm extends RefType { - JwtAlgorithm() { this.hasQualifiedName("com.auth0.jwt.algorithms", "Algorithm") } -} - -/** - * The interface `com.auth0.jwt.interfaces.JWTVerifier` or its implementation - * `com.auth0.jwt.JWTVerifier`. - */ -class JwtVerifier extends RefType { - JwtVerifier() { - this.hasQualifiedName(["com.auth0.jwt", "com.auth0.jwt.interfaces"], "JWTVerifier") - } -} - -/** A method that creates an instance of `com.auth0.jwt.algorithms.Algorithm`. */ -class GetAlgorithmMethod extends Method { - GetAlgorithmMethod() { - this.getDeclaringType() instanceof JwtAlgorithm and - this.getName().matches(["HMAC%", "ECDSA%", "RSA%"]) - } -} - -/** The `require` method of `com.auth0.jwt.JWT`. */ -class RequireMethod extends Method { - RequireMethod() { - this.getDeclaringType() instanceof Jwt and - this.hasName("require") - } -} - -/** The `sign` method of `com.auth0.jwt.JWTCreator.Builder`. */ -class SignTokenMethod extends Method { - SignTokenMethod() { - this.getDeclaringType() instanceof JwtBuilder and - this.hasName("sign") - } -} - -/** The `verify` method of `com.auth0.jwt.interfaces.JWTVerifier`. */ -class VerifyTokenMethod extends Method { - VerifyTokenMethod() { - this.getDeclaringType() instanceof JwtVerifier and - this.hasName("verify") - } -} - -/** - * A data flow source for JWT token signing vulnerabilities. - */ -abstract class JwtKeySource extends DataFlow::Node { } - -/** - * A data flow sink for JWT token signing vulnerabilities. - */ -abstract class JwtTokenSink extends DataFlow::Node { } - -/** - * A hardcoded string literal as a source for JWT token signing vulnerabilities. - */ -class HardcodedKeyStringSource extends JwtKeySource { - HardcodedKeyStringSource() { exists(this.asExpr().(CompileTimeConstantExpr).getStringValue()) } -} - -/** - * An expression used to sign JWT tokens as a sink of JWT token signing vulnerabilities. - */ -private class SignTokenSink extends JwtTokenSink { - SignTokenSink() { - exists(MethodAccess ma | - ma.getMethod() instanceof SignTokenMethod and - this.asExpr() = ma.getArgument(0) - ) - } -} - -/** - * An expression used to verify JWT tokens as a sink of JWT token signing vulnerabilities. - */ -private class VerifyTokenSink extends JwtTokenSink { - VerifyTokenSink() { - exists(MethodAccess ma | - ma.getMethod() instanceof VerifyTokenMethod and - this.asExpr() = ma.getQualifier() - ) - } -} - -/** - * A configuration depicting taint flow for checking JWT token signing vulnerabilities. - */ -class HardcodedJwtKeyConfiguration extends TaintTracking::Configuration { - HardcodedJwtKeyConfiguration() { this = "Hard-coded JWT Signing Key" } - - override predicate isSource(DataFlow::Node source) { source instanceof JwtKeySource } - - override predicate isSink(DataFlow::Node sink) { sink instanceof JwtTokenSink } - - override predicate isAdditionalTaintStep(DataFlow::Node prev, DataFlow::Node succ) { - exists(MethodAccess ma | - ( - ma.getMethod() instanceof GetAlgorithmMethod or - ma.getMethod() instanceof RequireMethod - ) and - prev.asExpr() = ma.getArgument(0) and - succ.asExpr() = ma - ) - } -} diff --git a/java/ql/src/utils/modelconverter/ExtractNeutrals.ql b/java/ql/src/utils/modelconverter/ExtractNeutrals.ql index ad7cef84f04..9b6a7e9d310 100644 --- a/java/ql/src/utils/modelconverter/ExtractNeutrals.ql +++ b/java/ql/src/utils/modelconverter/ExtractNeutrals.ql @@ -10,5 +10,5 @@ import semmle.code.java.dataflow.ExternalFlow from string package, string type, string name, string signature, string provenance where neutralModel(package, type, name, signature, provenance) and - provenance != "generated" + provenance != ["generated", "ai-generated"] select package, type, name, signature, provenance order by package, type, name, signature diff --git a/java/ql/src/utils/modelconverter/ExtractSinks.ql b/java/ql/src/utils/modelconverter/ExtractSinks.ql index cbfd4f7cebb..922554d3da3 100644 --- a/java/ql/src/utils/modelconverter/ExtractSinks.ql +++ b/java/ql/src/utils/modelconverter/ExtractSinks.ql @@ -12,6 +12,6 @@ from string input, string kind, string provenance where sinkModel(package, type, subtypes, name, signature, ext, input, kind, provenance) and - provenance != "generated" + provenance != ["generated", "ai-generated"] select package, type, subtypes, name, signature, ext, input, kind, provenance order by package, type, name, signature, input, kind diff --git a/java/ql/src/utils/modelconverter/ExtractSources.ql b/java/ql/src/utils/modelconverter/ExtractSources.ql index b74a97e3728..2723b7e35f4 100644 --- a/java/ql/src/utils/modelconverter/ExtractSources.ql +++ b/java/ql/src/utils/modelconverter/ExtractSources.ql @@ -12,6 +12,6 @@ from string output, string kind, string provenance where sourceModel(package, type, subtypes, name, signature, ext, output, kind, provenance) and - provenance != "generated" + provenance != ["generated", "ai-generated"] select package, type, subtypes, name, signature, ext, output, kind, provenance order by package, type, name, signature, output, kind diff --git a/java/ql/src/utils/modelconverter/ExtractSummaries.ql b/java/ql/src/utils/modelconverter/ExtractSummaries.ql index 65af62b3259..bdf9d551703 100644 --- a/java/ql/src/utils/modelconverter/ExtractSummaries.ql +++ b/java/ql/src/utils/modelconverter/ExtractSummaries.ql @@ -12,6 +12,6 @@ from string input, string output, string kind, string provenance where summaryModel(package, type, subtypes, name, signature, ext, input, output, kind, provenance) and - provenance != "generated" + provenance != ["generated", "ai-generated"] select package, type, subtypes, name, signature, ext, input, output, kind, provenance order by package, type, name, signature, input, output, kind diff --git a/java/ql/test/TestUtilities/InlineFlowTest.qll b/java/ql/test/TestUtilities/InlineFlowTest.qll index e6a0a551d15..550d08b1f18 100644 --- a/java/ql/test/TestUtilities/InlineFlowTest.qll +++ b/java/ql/test/TestUtilities/InlineFlowTest.qll @@ -47,6 +47,20 @@ private predicate defaultSource(DataFlow::Node src) { src.asExpr().(MethodAccess).getMethod().getName() = ["source", "taint"] } +module DefaultFlowConf implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node n) { defaultSource(n) } + + predicate isSink(DataFlow::Node n) { + exists(MethodAccess ma | ma.getMethod().hasName("sink") | n.asExpr() = ma.getAnArgument()) + } + + int fieldFlowBranchLimit() { result = 1000 } +} + +private module DefaultValueFlow = DataFlow::Make; + +private module DefaultTaintFlow = TaintTracking::Make; + class DefaultValueFlowConf extends DataFlow::Configuration { DefaultValueFlowConf() { this = "qltest:defaultValueFlowConf" } @@ -76,6 +90,8 @@ private string getSourceArgString(DataFlow::Node src) { src.asExpr().(MethodAccess).getAnArgument().(StringLiteral).getValue() = result } +abstract class EnableLegacyConfiguration extends Unit { } + class InlineFlowTest extends InlineExpectationsTest { InlineFlowTest() { this = "HasFlowTest" } @@ -83,7 +99,7 @@ class InlineFlowTest extends InlineExpectationsTest { override predicate hasActualResult(Location location, string element, string tag, string value) { tag = "hasValueFlow" and - exists(DataFlow::Node src, DataFlow::Node sink | getValueFlowConfig().hasFlow(src, sink) | + exists(DataFlow::Node src, DataFlow::Node sink | hasValueFlow(src, sink) | sink.getLocation() = location and element = sink.toString() and if exists(getSourceArgString(src)) then value = getSourceArgString(src) else value = "" @@ -91,7 +107,7 @@ class InlineFlowTest extends InlineExpectationsTest { or tag = "hasTaintFlow" and exists(DataFlow::Node src, DataFlow::Node sink | - getTaintFlowConfig().hasFlow(src, sink) and not getValueFlowConfig().hasFlow(src, sink) + hasTaintFlow(src, sink) and not hasValueFlow(src, sink) | sink.getLocation() = location and element = sink.toString() and @@ -99,6 +115,18 @@ class InlineFlowTest extends InlineExpectationsTest { ) } + predicate hasValueFlow(DataFlow::Node src, DataFlow::Node sink) { + if exists(EnableLegacyConfiguration e) + then getValueFlowConfig().hasFlow(src, sink) + else DefaultValueFlow::hasFlow(src, sink) + } + + predicate hasTaintFlow(DataFlow::Node src, DataFlow::Node sink) { + if exists(EnableLegacyConfiguration e) + then getTaintFlowConfig().hasFlow(src, sink) + else DefaultTaintFlow::hasFlow(src, sink) + } + DataFlow::Configuration getValueFlowConfig() { result = any(DefaultValueFlowConf config) } DataFlow::Configuration getTaintFlowConfig() { result = any(DefaultTaintFlowConf config) } diff --git a/java/ql/test/experimental/query-tests/security/CWE-321/HardcodedJwtKey.expected b/java/ql/test/experimental/query-tests/security/CWE-321/HardcodedJwtKey.expected deleted file mode 100644 index 15e882db4cd..00000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-321/HardcodedJwtKey.expected +++ /dev/null @@ -1,25 +0,0 @@ -edges -| HardcodedJwtKey.java:15:33:15:38 | SECRET : String | HardcodedJwtKey.java:19:49:19:54 | SECRET : String | -| HardcodedJwtKey.java:15:33:15:38 | SECRET : String | HardcodedJwtKey.java:42:62:42:67 | SECRET : String | -| HardcodedJwtKey.java:15:42:15:59 | "hardcoded_secret" : String | HardcodedJwtKey.java:15:33:15:38 | SECRET : String | -| HardcodedJwtKey.java:19:49:19:54 | SECRET : String | HardcodedJwtKey.java:25:23:25:31 | algorithm | -| HardcodedJwtKey.java:42:32:42:69 | require(...) : Verification | HardcodedJwtKey.java:42:32:43:35 | withIssuer(...) : Verification | -| HardcodedJwtKey.java:42:32:43:35 | withIssuer(...) : Verification | HardcodedJwtKey.java:42:32:44:24 | build(...) : JWTVerifier | -| HardcodedJwtKey.java:42:32:44:24 | build(...) : JWTVerifier | HardcodedJwtKey.java:46:13:46:20 | verifier | -| HardcodedJwtKey.java:42:62:42:67 | SECRET : String | HardcodedJwtKey.java:42:32:42:69 | require(...) : Verification | -nodes -| HardcodedJwtKey.java:15:33:15:38 | SECRET : String | semmle.label | SECRET : String | -| HardcodedJwtKey.java:15:42:15:59 | "hardcoded_secret" : String | semmle.label | "hardcoded_secret" : String | -| HardcodedJwtKey.java:19:49:19:54 | SECRET : String | semmle.label | SECRET : String | -| HardcodedJwtKey.java:25:23:25:31 | algorithm | semmle.label | algorithm | -| HardcodedJwtKey.java:42:32:42:69 | require(...) : Verification | semmle.label | require(...) : Verification | -| HardcodedJwtKey.java:42:32:43:35 | withIssuer(...) : Verification | semmle.label | withIssuer(...) : Verification | -| HardcodedJwtKey.java:42:32:44:24 | build(...) : JWTVerifier | semmle.label | build(...) : JWTVerifier | -| HardcodedJwtKey.java:42:62:42:67 | SECRET : String | semmle.label | SECRET : String | -| HardcodedJwtKey.java:46:13:46:20 | verifier | semmle.label | verifier | -subpaths -#select -| HardcodedJwtKey.java:25:23:25:31 | algorithm | HardcodedJwtKey.java:15:42:15:59 | "hardcoded_secret" : String | HardcodedJwtKey.java:25:23:25:31 | algorithm | $@ is used to sign a JWT token. | HardcodedJwtKey.java:15:42:15:59 | "hardcoded_secret" | Hardcoded String | -| HardcodedJwtKey.java:25:23:25:31 | algorithm | HardcodedJwtKey.java:19:49:19:54 | SECRET : String | HardcodedJwtKey.java:25:23:25:31 | algorithm | $@ is used to sign a JWT token. | HardcodedJwtKey.java:19:49:19:54 | SECRET | Hardcoded String | -| HardcodedJwtKey.java:46:13:46:20 | verifier | HardcodedJwtKey.java:15:42:15:59 | "hardcoded_secret" : String | HardcodedJwtKey.java:46:13:46:20 | verifier | $@ is used to sign a JWT token. | HardcodedJwtKey.java:15:42:15:59 | "hardcoded_secret" | Hardcoded String | -| HardcodedJwtKey.java:46:13:46:20 | verifier | HardcodedJwtKey.java:42:62:42:67 | SECRET : String | HardcodedJwtKey.java:46:13:46:20 | verifier | $@ is used to sign a JWT token. | HardcodedJwtKey.java:42:62:42:67 | SECRET | Hardcoded String | diff --git a/java/ql/test/experimental/query-tests/security/CWE-321/HardcodedJwtKey.qlref b/java/ql/test/experimental/query-tests/security/CWE-321/HardcodedJwtKey.qlref deleted file mode 100644 index 3da970cd380..00000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-321/HardcodedJwtKey.qlref +++ /dev/null @@ -1 +0,0 @@ -experimental/Security/CWE/CWE-321/HardcodedJwtKey.ql \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-321/options b/java/ql/test/experimental/query-tests/security/CWE-321/options deleted file mode 100644 index ab6ca411a02..00000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-321/options +++ /dev/null @@ -1 +0,0 @@ -//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/auth0-jwt-2.3 diff --git a/java/ql/test/library-tests/dataflow/collections/containerflow.ql b/java/ql/test/library-tests/dataflow/collections/containerflow.ql index df1f397f059..992971d6dc7 100644 --- a/java/ql/test/library-tests/dataflow/collections/containerflow.ql +++ b/java/ql/test/library-tests/dataflow/collections/containerflow.ql @@ -1,7 +1,3 @@ import java import semmle.code.java.dataflow.DataFlow import TestUtilities.InlineFlowTest - -class HasFlowTest extends InlineFlowTest { - override DataFlow::Configuration getTaintFlowConfig() { none() } -} diff --git a/java/ql/test/library-tests/dataflow/fluent-methods/Test.java b/java/ql/test/library-tests/dataflow/fluent-methods/Test.java index 5a0d3a44c2f..d776d85a5f6 100644 --- a/java/ql/test/library-tests/dataflow/fluent-methods/Test.java +++ b/java/ql/test/library-tests/dataflow/fluent-methods/Test.java @@ -42,31 +42,31 @@ public class Test { public static void test1() { Test t = new Test(); t.fluentNoop().fluentSet(source()).fluentNoop(); - sink(t.get()); // $hasTaintFlow + sink(t.get()); // $hasValueFlow } public static void test2() { Test t = new Test(); Test.identity(t).fluentNoop().fluentSet(source()).fluentNoop(); - sink(t.get()); // $hasTaintFlow + sink(t.get()); // $hasValueFlow } public static void test3() { Test t = new Test(); t.indirectlyFluentNoop().fluentSet(source()).fluentNoop(); - sink(t.get()); // $hasTaintFlow + sink(t.get()); // $hasValueFlow } public static void testModel1() { Test t = new Test(); t.indirectlyFluentNoop().modelledFluentMethod().fluentSet(source()).fluentNoop(); - sink(t.get()); // $hasTaintFlow + sink(t.get()); // $hasValueFlow } public static void testModel2() { Test t = new Test(); Test.modelledIdentity(t).indirectlyFluentNoop().modelledFluentMethod().fluentSet(source()).fluentNoop(); - sink(t.get()); // $hasTaintFlow + sink(t.get()); // $hasValueFlow } } diff --git a/java/ql/test/library-tests/dataflow/fluent-methods/flow.ql b/java/ql/test/library-tests/dataflow/fluent-methods/flow.ql index dfdf41d7b98..4a9794ab351 100644 --- a/java/ql/test/library-tests/dataflow/fluent-methods/flow.ql +++ b/java/ql/test/library-tests/dataflow/fluent-methods/flow.ql @@ -12,7 +12,3 @@ class IdentityModel extends ValuePreservingMethod { override predicate returnsValue(int arg) { arg = 0 } } - -class HasFlowTest extends InlineFlowTest { - override DataFlow::Configuration getValueFlowConfig() { none() } -} diff --git a/java/ql/test/library-tests/dataflow/inoutbarriers/test.ql b/java/ql/test/library-tests/dataflow/inoutbarriers/test.ql index 03c1290b172..a582f5b2456 100644 --- a/java/ql/test/library-tests/dataflow/inoutbarriers/test.ql +++ b/java/ql/test/library-tests/dataflow/inoutbarriers/test.ql @@ -14,54 +14,46 @@ predicate sink0(Node n) { ) } -class Conf1 extends Configuration { - Conf1() { this = "inoutbarriers1" } +module Conf1 implements ConfigSig { + predicate isSource(Node n) { src0(n) } - override predicate isSource(Node n) { src0(n) } - - override predicate isSink(Node n) { sink0(n) } + predicate isSink(Node n) { sink0(n) } } -class Conf2 extends Configuration { - Conf2() { this = "inoutbarriers2" } +module Conf2 implements ConfigSig { + predicate isSource(Node n) { src0(n) } - override predicate isSource(Node n) { src0(n) } + predicate isSink(Node n) { sink0(n) } - override predicate isSink(Node n) { sink0(n) } - - override predicate isBarrierIn(Node n) { src0(n) } + predicate isBarrierIn(Node n) { src0(n) } } -class Conf3 extends Configuration { - Conf3() { this = "inoutbarriers3" } +module Conf3 implements ConfigSig { + predicate isSource(Node n) { src0(n) } - override predicate isSource(Node n) { src0(n) } + predicate isSink(Node n) { sink0(n) } - override predicate isSink(Node n) { sink0(n) } - - override predicate isBarrierOut(Node n) { sink0(n) } + predicate isBarrierOut(Node n) { sink0(n) } } -class Conf4 extends Configuration { - Conf4() { this = "inoutbarriers4" } +module Conf4 implements ConfigSig { + predicate isSource(Node n) { src0(n) } - override predicate isSource(Node n) { src0(n) } + predicate isSink(Node n) { sink0(n) } - override predicate isSink(Node n) { sink0(n) } + predicate isBarrierIn(Node n) { src0(n) } - override predicate isBarrierIn(Node n) { src0(n) } - - override predicate isBarrierOut(Node n) { sink0(n) } + predicate isBarrierOut(Node n) { sink0(n) } } predicate flow(Node src, Node sink, string s) { - any(Conf1 c).hasFlow(src, sink) and s = "nobarrier" + Make::hasFlow(src, sink) and s = "nobarrier" or - any(Conf2 c).hasFlow(src, sink) and s = "srcbarrier" + Make::hasFlow(src, sink) and s = "srcbarrier" or - any(Conf3 c).hasFlow(src, sink) and s = "sinkbarrier" + Make::hasFlow(src, sink) and s = "sinkbarrier" or - any(Conf4 c).hasFlow(src, sink) and s = "both" + Make::hasFlow(src, sink) and s = "both" } from Node src, Node sink, string s diff --git a/java/ql/test/library-tests/dataflow/partial/test.ql b/java/ql/test/library-tests/dataflow/partial/test.ql index 5aacbb25832..399a876e9a9 100644 --- a/java/ql/test/library-tests/dataflow/partial/test.ql +++ b/java/ql/test/library-tests/dataflow/partial/test.ql @@ -1,18 +1,19 @@ import java import semmle.code.java.dataflow.DataFlow import DataFlow -import PartialPathGraph -class Conf extends Configuration { - Conf() { this = "partial flow" } +module Config implements ConfigSig { + predicate isSource(Node n) { n.asExpr().(MethodAccess).getMethod().hasName("src") } - override predicate isSource(Node n) { n.asExpr().(MethodAccess).getMethod().hasName("src") } - - override predicate isSink(Node n) { n.asExpr().(Argument).getCall().getCallee().hasName("sink") } - - override int explorationLimit() { result = 10 } + predicate isSink(Node n) { n.asExpr().(Argument).getCall().getCallee().hasName("sink") } } -from PartialPathNode n, int dist -where any(Conf c).hasPartialFlow(_, n, dist) +int explorationLimit() { result = 10 } + +module PartialFlow = Make::FlowExploration; + +import PartialFlow::PartialPathGraph + +from PartialFlow::PartialPathNode n, int dist +where PartialFlow::hasPartialFlow(_, n, dist) select dist, n diff --git a/java/ql/test/library-tests/dataflow/partial/testRev.ql b/java/ql/test/library-tests/dataflow/partial/testRev.ql index 9855b9bdcd1..c869cb4f915 100644 --- a/java/ql/test/library-tests/dataflow/partial/testRev.ql +++ b/java/ql/test/library-tests/dataflow/partial/testRev.ql @@ -1,18 +1,19 @@ import java import semmle.code.java.dataflow.DataFlow import DataFlow -import PartialPathGraph -class Conf extends Configuration { - Conf() { this = "partial flow" } +module Config implements ConfigSig { + predicate isSource(Node n) { n.asExpr().(MethodAccess).getMethod().hasName("src") } - override predicate isSource(Node n) { n.asExpr().(MethodAccess).getMethod().hasName("src") } - - override predicate isSink(Node n) { n.asExpr().(Argument).getCall().getCallee().hasName("sink") } - - override int explorationLimit() { result = 10 } + predicate isSink(Node n) { n.asExpr().(Argument).getCall().getCallee().hasName("sink") } } -from PartialPathNode n, int dist -where any(Conf c).hasPartialFlowRev(n, _, dist) +int explorationLimit() { result = 10 } + +module PartialFlow = Make::FlowExploration; + +import PartialFlow::PartialPathGraph + +from PartialFlow::PartialPathNode n, int dist +where PartialFlow::hasPartialFlowRev(n, _, dist) select dist, n diff --git a/java/ql/test/library-tests/dataflow/state/test.ql b/java/ql/test/library-tests/dataflow/state/test.ql index d80f017246e..64abca4c3ab 100644 --- a/java/ql/test/library-tests/dataflow/state/test.ql +++ b/java/ql/test/library-tests/dataflow/state/test.ql @@ -39,22 +39,26 @@ predicate step(Node n1, Node n2, string s1, string s2) { predicate checkNode(Node n) { n.asExpr().(Argument).getCall().getCallee().hasName("check") } -class Conf extends TaintTracking::Configuration { - Conf() { this = "qltest:state" } +module Conf implements DataFlow::StateConfigSig { + class FlowState = string; - override predicate isSource(Node n, FlowState s) { src(n, s) } + predicate isSource(Node n, FlowState s) { src(n, s) } - override predicate isSink(Node n, FlowState s) { sink(n, s) } + predicate isSink(Node n, FlowState s) { sink(n, s) } - override predicate isSanitizer(Node n, FlowState s) { bar(n, s) } + predicate isBarrier(Node n, FlowState s) { bar(n, s) } - override predicate isAdditionalTaintStep(Node n1, FlowState s1, Node n2, FlowState s2) { + predicate isAdditionalFlowStep(Node n1, FlowState s1, Node n2, FlowState s2) { step(n1, n2, s1, s2) } - - override int explorationLimit() { result = 0 } } +int explorationLimit() { result = 0 } + +module Flow = TaintTracking::MakeWithState; + +module PartialFlow = Flow::FlowExploration; + class HasFlowTest extends InlineExpectationsTest { HasFlowTest() { this = "HasFlowTest" } @@ -62,16 +66,16 @@ class HasFlowTest extends InlineExpectationsTest { override predicate hasActualResult(Location location, string element, string tag, string value) { tag = "flow" and - exists(PathNode src, PathNode sink, Conf conf | - conf.hasFlowPath(src, sink) and + exists(Flow::PathNode src, Flow::PathNode sink | + Flow::hasFlowPath(src, sink) and sink.getNode().getLocation() = location and element = sink.toString() and value = src.getState() ) or tag = "pFwd" and - exists(PartialPathNode src, PartialPathNode node, Conf conf | - conf.hasPartialFlow(src, node, _) and + exists(PartialFlow::PartialPathNode src, PartialFlow::PartialPathNode node | + PartialFlow::hasPartialFlow(src, node, _) and checkNode(node.getNode()) and node.getNode().getLocation() = location and element = node.toString() and @@ -79,8 +83,8 @@ class HasFlowTest extends InlineExpectationsTest { ) or tag = "pRev" and - exists(PartialPathNode node, PartialPathNode sink, Conf conf | - conf.hasPartialFlowRev(node, sink, _) and + exists(PartialFlow::PartialPathNode node, PartialFlow::PartialPathNode sink | + PartialFlow::hasPartialFlowRev(node, sink, _) and checkNode(node.getNode()) and node.getNode().getLocation() = location and element = node.toString() and diff --git a/java/ql/test/library-tests/dataflow/taint-format/A.java b/java/ql/test/library-tests/dataflow/taint-format/A.java index 6236451af38..073a31f3685 100644 --- a/java/ql/test/library-tests/dataflow/taint-format/A.java +++ b/java/ql/test/library-tests/dataflow/taint-format/A.java @@ -6,42 +6,44 @@ class A { return "tainted"; } + public static void sink(Object o) { } + public static void test1() { - String bad = source(); // $ hasTaintFlow + String bad = source(); String good = "hi"; - bad.formatted(good); // $ hasTaintFlow - good.formatted("a", bad, "b", good); // $ hasTaintFlow - String.format("%s%s", bad, good); // $ hasTaintFlow - String.format("%s", good); - String.format("%s %s %s %s %s %s %s %s %s %s ", "a", "a", "a", "a", "a", "a", "a", "a", "a", bad); // $ hasTaintFlow + sink(bad.formatted(good)); // $ hasTaintFlow + sink(good.formatted("a", bad, "b", good)); // $ hasTaintFlow + sink(String.format("%s%s", bad, good)); // $ hasTaintFlow + sink(String.format("%s", good)); + sink(String.format("%s %s %s %s %s %s %s %s %s %s ", "a", "a", "a", "a", "a", "a", "a", "a", "a", bad)); // $ hasTaintFlow } public static void test2() { - String bad = source(); // $ hasTaintFlow + String bad = source(); Formatter f = new Formatter(); - f.toString(); - f.format("%s", bad); // $ hasTaintFlow - f.toString(); // $ hasTaintFlow + sink(f.toString()); + sink(f.format("%s", bad)); // $ hasTaintFlow + sink(f.toString()); // $ hasTaintFlow } public static void test3() { - String bad = source(); // $ hasTaintFlow + String bad = source(); StringBuilder sb = new StringBuilder(); Formatter f = new Formatter(sb); - sb.toString(); // $ hasTaintFlow false positive - f.format("%s", bad); // $ hasTaintFlow - sb.toString(); // $ hasTaintFlow + sink(sb.toString()); // $ SPURIOUS: hasTaintFlow + sink(f.format("%s", bad)); // $ hasTaintFlow + sink(sb.toString()); // $ hasTaintFlow } public static void test4() { - String bad = source(); // $ hasTaintFlow + String bad = source(); StringBuilder sb = new StringBuilder(); - sb.append(bad); // $ hasTaintFlow + sink(sb.append(bad)); // $ hasTaintFlow - new Formatter(sb).format("ok").toString(); // $ hasTaintFlow + sink(new Formatter(sb).format("ok").toString()); // $ hasTaintFlow } -} \ No newline at end of file +} diff --git a/java/ql/test/library-tests/dataflow/taint-format/test.ql b/java/ql/test/library-tests/dataflow/taint-format/test.ql index 9456e0fbddc..5d91e4e8e26 100644 --- a/java/ql/test/library-tests/dataflow/taint-format/test.ql +++ b/java/ql/test/library-tests/dataflow/taint-format/test.ql @@ -1,8 +1,2 @@ import java -import semmle.code.java.dataflow.DataFlow -import semmle.code.java.dataflow.TaintTracking import TestUtilities.InlineFlowTest - -class TaintFlowConf extends DefaultTaintFlowConf { - override predicate isSink(DataFlow::Node n) { n instanceof DataFlow::ExprNode } -} diff --git a/java/ql/test/library-tests/frameworks/JaxWs/JaxRsFlow.ql b/java/ql/test/library-tests/frameworks/JaxWs/JaxRsFlow.ql index c0c0bd11804..dcfe48d08bb 100644 --- a/java/ql/test/library-tests/frameworks/JaxWs/JaxRsFlow.ql +++ b/java/ql/test/library-tests/frameworks/JaxWs/JaxRsFlow.ql @@ -3,6 +3,10 @@ import semmle.code.java.dataflow.TaintTracking import semmle.code.java.dataflow.FlowSources import TestUtilities.InlineFlowTest +class EnableLegacy extends EnableLegacyConfiguration { + EnableLegacy() { exists(this) } +} + class TaintFlowConf extends DefaultTaintFlowConf { override predicate isSource(DataFlow::Node n) { super.isSource(n) diff --git a/java/ql/test/library-tests/frameworks/android/content-provider/test.ql b/java/ql/test/library-tests/frameworks/android/content-provider/test.ql index 05593052acf..a88702a206f 100644 --- a/java/ql/test/library-tests/frameworks/android/content-provider/test.ql +++ b/java/ql/test/library-tests/frameworks/android/content-provider/test.ql @@ -2,6 +2,10 @@ import java import semmle.code.java.dataflow.FlowSources import TestUtilities.InlineFlowTest +class EnableLegacy extends EnableLegacyConfiguration { + EnableLegacy() { exists(this) } +} + class ProviderTaintFlowConf extends DefaultTaintFlowConf { override predicate isSource(DataFlow::Node n) { n instanceof RemoteFlowSource } } diff --git a/java/ql/test/library-tests/frameworks/android/external-storage/test.ql b/java/ql/test/library-tests/frameworks/android/external-storage/test.ql index 03509c2d46d..e7d5ae7e44b 100644 --- a/java/ql/test/library-tests/frameworks/android/external-storage/test.ql +++ b/java/ql/test/library-tests/frameworks/android/external-storage/test.ql @@ -3,6 +3,10 @@ import semmle.code.java.dataflow.DataFlow import semmle.code.java.dataflow.FlowSources import TestUtilities.InlineFlowTest +class EnableLegacy extends EnableLegacyConfiguration { + EnableLegacy() { exists(this) } +} + class Conf extends TaintTracking::Configuration { Conf() { this = "test:AndroidExternalFlowConf" } diff --git a/java/ql/test/library-tests/frameworks/android/slice/test.ql b/java/ql/test/library-tests/frameworks/android/slice/test.ql index 6c328bc5a8b..5274c08ccdd 100644 --- a/java/ql/test/library-tests/frameworks/android/slice/test.ql +++ b/java/ql/test/library-tests/frameworks/android/slice/test.ql @@ -2,6 +2,10 @@ import java import TestUtilities.InlineFlowTest import semmle.code.java.dataflow.FlowSources +class EnableLegacy extends EnableLegacyConfiguration { + EnableLegacy() { exists(this) } +} + class SliceValueFlowConf extends DefaultValueFlowConf { override predicate isSource(DataFlow::Node source) { super.isSource(source) or source instanceof RemoteFlowSource diff --git a/java/ql/test/library-tests/frameworks/android/sources/OnActivityResultSourceTest.ql b/java/ql/test/library-tests/frameworks/android/sources/OnActivityResultSourceTest.ql index e8c498bebdf..682442b3dfc 100644 --- a/java/ql/test/library-tests/frameworks/android/sources/OnActivityResultSourceTest.ql +++ b/java/ql/test/library-tests/frameworks/android/sources/OnActivityResultSourceTest.ql @@ -2,6 +2,10 @@ import java import semmle.code.java.dataflow.FlowSources import TestUtilities.InlineFlowTest +class EnableLegacy extends EnableLegacyConfiguration { + EnableLegacy() { exists(this) } +} + class SourceValueFlowConf extends DefaultValueFlowConf { override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource } } diff --git a/java/ql/test/library-tests/frameworks/apache-http/flow.ql b/java/ql/test/library-tests/frameworks/apache-http/flow.ql index 6a9c51f4b10..ac350ec6eb9 100644 --- a/java/ql/test/library-tests/frameworks/apache-http/flow.ql +++ b/java/ql/test/library-tests/frameworks/apache-http/flow.ql @@ -5,6 +5,10 @@ import semmle.code.java.security.XSS import semmle.code.java.security.UrlRedirect import TestUtilities.InlineFlowTest +class EnableLegacy extends EnableLegacyConfiguration { + EnableLegacy() { exists(this) } +} + class Conf extends TaintTracking::Configuration { Conf() { this = "qltest:frameworks:apache-http" } diff --git a/java/ql/test/library-tests/frameworks/okhttp/test.ql b/java/ql/test/library-tests/frameworks/okhttp/test.ql index e78af7d25bf..063f6dc4fe8 100644 --- a/java/ql/test/library-tests/frameworks/okhttp/test.ql +++ b/java/ql/test/library-tests/frameworks/okhttp/test.ql @@ -1,6 +1,10 @@ import java import TestUtilities.InlineFlowTest +class EnableLegacy extends EnableLegacyConfiguration { + EnableLegacy() { exists(this) } +} + class FlowConf extends DefaultValueFlowConf { override predicate isSink(DataFlow::Node n) { super.isSink(n) or sinkNode(n, "open-url") } } diff --git a/java/ql/test/library-tests/frameworks/rabbitmq/FlowTest.ql b/java/ql/test/library-tests/frameworks/rabbitmq/FlowTest.ql index 9f7484028af..6e200ffa201 100644 --- a/java/ql/test/library-tests/frameworks/rabbitmq/FlowTest.ql +++ b/java/ql/test/library-tests/frameworks/rabbitmq/FlowTest.ql @@ -3,6 +3,10 @@ import semmle.code.java.dataflow.TaintTracking import semmle.code.java.dataflow.FlowSources import TestUtilities.InlineFlowTest +class EnableLegacy extends EnableLegacyConfiguration { + EnableLegacy() { exists(this) } +} + class Conf extends TaintTracking::Configuration { Conf() { this = "qltest:frameworks:rabbitmq" } diff --git a/java/ql/test/library-tests/frameworks/retrofit/test.ql b/java/ql/test/library-tests/frameworks/retrofit/test.ql index e78af7d25bf..063f6dc4fe8 100644 --- a/java/ql/test/library-tests/frameworks/retrofit/test.ql +++ b/java/ql/test/library-tests/frameworks/retrofit/test.ql @@ -1,6 +1,10 @@ import java import TestUtilities.InlineFlowTest +class EnableLegacy extends EnableLegacyConfiguration { + EnableLegacy() { exists(this) } +} + class FlowConf extends DefaultValueFlowConf { override predicate isSink(DataFlow::Node n) { super.isSink(n) or sinkNode(n, "open-url") } } diff --git a/java/ql/test/library-tests/frameworks/spring/controller/test.ql b/java/ql/test/library-tests/frameworks/spring/controller/test.ql index fb180557a8c..bfd5384454b 100644 --- a/java/ql/test/library-tests/frameworks/spring/controller/test.ql +++ b/java/ql/test/library-tests/frameworks/spring/controller/test.ql @@ -2,6 +2,10 @@ import java import semmle.code.java.dataflow.FlowSources import TestUtilities.InlineFlowTest +class EnableLegacy extends EnableLegacyConfiguration { + EnableLegacy() { exists(this) } +} + class ValueFlowConf extends DataFlow::Configuration { ValueFlowConf() { this = "ValueFlowConf" } diff --git a/java/ql/test/library-tests/pathsanitizer/test.ql b/java/ql/test/library-tests/pathsanitizer/test.ql index c3b8ca70367..bea3af839a1 100644 --- a/java/ql/test/library-tests/pathsanitizer/test.ql +++ b/java/ql/test/library-tests/pathsanitizer/test.ql @@ -2,6 +2,10 @@ import java import semmle.code.java.security.PathSanitizer import TestUtilities.InlineFlowTest +class EnableLegacy extends EnableLegacyConfiguration { + EnableLegacy() { exists(this) } +} + class PathSanitizerConf extends DefaultTaintFlowConf { override predicate isSanitizer(DataFlow::Node sanitizer) { sanitizer instanceof PathInjectionSanitizer diff --git a/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.expected b/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.expected index efd8132e166..e9262c2b035 100644 --- a/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.expected +++ b/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.expected @@ -14,6 +14,7 @@ edges | Test.java:95:14:95:34 | getHostName(...) : String | Test.java:99:12:99:33 | new URI(...) | | Test.java:95:14:95:34 | getHostName(...) : String | Test.java:100:12:100:45 | new URI(...) | | Test.java:95:14:95:34 | getHostName(...) : String | Test.java:101:12:101:54 | new URI(...) | +| Test.java:105:14:105:34 | getHostName(...) : String | Test.java:107:46:107:46 | t | nodes | Test.java:19:18:19:38 | getHostName(...) : String | semmle.label | getHostName(...) : String | | Test.java:24:20:24:23 | temp | semmle.label | temp | @@ -34,6 +35,8 @@ nodes | Test.java:99:12:99:33 | new URI(...) | semmle.label | new URI(...) | | Test.java:100:12:100:45 | new URI(...) | semmle.label | new URI(...) | | Test.java:101:12:101:54 | new URI(...) | semmle.label | new URI(...) | +| Test.java:105:14:105:34 | getHostName(...) : String | semmle.label | getHostName(...) : String | +| Test.java:107:46:107:46 | t | semmle.label | t | subpaths #select | Test.java:24:11:24:24 | new File(...) | Test.java:19:18:19:38 | getHostName(...) : String | Test.java:24:20:24:23 | temp | This path depends on a $@. | Test.java:19:18:19:38 | getHostName(...) | user-provided value | @@ -47,3 +50,4 @@ subpaths | Test.java:99:3:99:34 | new File(...) | Test.java:95:14:95:34 | getHostName(...) : String | Test.java:99:12:99:33 | new URI(...) | This path depends on a $@. | Test.java:95:14:95:34 | getHostName(...) | user-provided value | | Test.java:100:3:100:46 | new File(...) | Test.java:95:14:95:34 | getHostName(...) : String | Test.java:100:12:100:45 | new URI(...) | This path depends on a $@. | Test.java:95:14:95:34 | getHostName(...) | user-provided value | | Test.java:101:3:101:55 | new File(...) | Test.java:95:14:95:34 | getHostName(...) : String | Test.java:101:12:101:54 | new URI(...) | This path depends on a $@. | Test.java:95:14:95:34 | getHostName(...) | user-provided value | +| Test.java:107:46:107:46 | t | Test.java:105:14:105:34 | getHostName(...) : String | Test.java:107:46:107:46 | t | This path depends on a $@. | Test.java:105:14:105:34 | getHostName(...) | user-provided value | diff --git a/java/ql/test/query-tests/security/CWE-022/semmle/tests/Test.java b/java/ql/test/query-tests/security/CWE-022/semmle/tests/Test.java index 46c3eeec001..626851215e0 100644 --- a/java/ql/test/query-tests/security/CWE-022/semmle/tests/Test.java +++ b/java/ql/test/query-tests/security/CWE-022/semmle/tests/Test.java @@ -100,4 +100,10 @@ class Test { new File(new URI(null, null, t, null, null)); new File(new URI(null, null, null, 0, t, null, null)); } + + void doGet6(InetAddress address) throws IOException { + String t = address.getHostName(); + // BAD: accessing local resource with user input + getClass().getModule().getResourceAsStream(t); + } } diff --git a/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversalFromRemoteTest.ql b/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversalFromRemoteTest.ql index b4009b55244..7f441e62d4e 100644 --- a/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversalFromRemoteTest.ql +++ b/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversalFromRemoteTest.ql @@ -2,6 +2,10 @@ import java import TestUtilities.InlineFlowTest import semmle.code.java.security.PartialPathTraversalQuery +class EnableLegacy extends EnableLegacyConfiguration { + EnableLegacy() { exists(this) } +} + class TestRemoteSource extends RemoteFlowSource { TestRemoteSource() { this.asParameter().hasName(["dir", "path"]) } diff --git a/java/ql/test/query-tests/security/CWE-089/semmle/examples/Hive.java b/java/ql/test/query-tests/security/CWE-089/semmle/examples/Hive.java new file mode 100644 index 00000000000..0adbd3e2c52 --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-089/semmle/examples/Hive.java @@ -0,0 +1,29 @@ +import org.apache.hadoop.hive.metastore.api.ColumnStatistics; +import org.apache.hadoop.hive.metastore.api.DefaultConstraintsRequest; +import org.apache.hadoop.hive.metastore.ObjectStore; +import org.apache.hive.hcatalog.templeton.ColumnDesc; +import org.apache.hive.hcatalog.templeton.HcatDelegator; +import java.util.List; + +public class Hive { + + public static Object source() { + return null; + } + + public void test(ObjectStore objStore, HcatDelegator hcatDel) throws Exception { + { + String taint = (String) source(); + new DefaultConstraintsRequest("", taint, ""); // $ sqlInjection + } + { + ColumnStatistics taint = (ColumnStatistics) source(); + //objStore.updatePartitionColumnStatistics(taint, (List) null, (String) null, 0L); // $ sqlInjection + objStore.updatePartitionColumnStatistics(taint, (List) null); // $ sqlInjection + } + { + ColumnDesc taint = (ColumnDesc) source(); + hcatDel.addOneColumn(null, null, null, taint); // $ sqlInjection + } + } +} diff --git a/java/ql/test/query-tests/security/CWE-089/semmle/examples/options b/java/ql/test/query-tests/security/CWE-089/semmle/examples/options index 7427669b447..1b24aa77776 100644 --- a/java/ql/test/query-tests/security/CWE-089/semmle/examples/options +++ b/java/ql/test/query-tests/security/CWE-089/semmle/examples/options @@ -1 +1 @@ -//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/mongodbClient:${testdir}/../../../../../stubs/springframework-5.3.8 +//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/mongodbClient:${testdir}/../../../../../stubs/springframework-5.3.8:${testdir}/../../../../../stubs/apache-hive diff --git a/java/ql/test/query-tests/security/CWE-117/LogInjectionTest.ql b/java/ql/test/query-tests/security/CWE-117/LogInjectionTest.ql index 93110ec2006..629266279ed 100644 --- a/java/ql/test/query-tests/security/CWE-117/LogInjectionTest.ql +++ b/java/ql/test/query-tests/security/CWE-117/LogInjectionTest.ql @@ -2,6 +2,10 @@ import java import semmle.code.java.security.LogInjectionQuery import TestUtilities.InlineFlowTest +class EnableLegacy extends EnableLegacyConfiguration { + EnableLegacy() { exists(this) } +} + private class TestSource extends RemoteFlowSource { TestSource() { this.asExpr().(MethodAccess).getMethod().hasName("source") } diff --git a/java/ql/test/query-tests/security/CWE-266/IntentUriPermissionManipulationTest.ql b/java/ql/test/query-tests/security/CWE-266/IntentUriPermissionManipulationTest.ql index 72b95d874d2..62d3ce3ac3c 100644 --- a/java/ql/test/query-tests/security/CWE-266/IntentUriPermissionManipulationTest.ql +++ b/java/ql/test/query-tests/security/CWE-266/IntentUriPermissionManipulationTest.ql @@ -2,6 +2,10 @@ import java import TestUtilities.InlineFlowTest import semmle.code.java.security.IntentUriPermissionManipulationQuery +class EnableLegacy extends EnableLegacyConfiguration { + EnableLegacy() { exists(this) } +} + class IntentUriPermissionManipulationTest extends InlineFlowTest { override DataFlow::Configuration getValueFlowConfig() { none() } diff --git a/java/ql/test/query-tests/security/CWE-295/InsecureTrustManager/InsecureTrustManagerTest.ql b/java/ql/test/query-tests/security/CWE-295/InsecureTrustManager/InsecureTrustManagerTest.ql index b2922d13d69..fb00ff33b34 100644 --- a/java/ql/test/query-tests/security/CWE-295/InsecureTrustManager/InsecureTrustManagerTest.ql +++ b/java/ql/test/query-tests/security/CWE-295/InsecureTrustManager/InsecureTrustManagerTest.ql @@ -2,6 +2,10 @@ import java import semmle.code.java.security.InsecureTrustManagerQuery import TestUtilities.InlineFlowTest +class EnableLegacy extends EnableLegacyConfiguration { + EnableLegacy() { exists(this) } +} + class InsecureTrustManagerTest extends InlineFlowTest { override DataFlow::Configuration getValueFlowConfig() { result = any(InsecureTrustManagerConfiguration c) diff --git a/java/ql/test/query-tests/security/CWE-441/UnsafeContentUriResolutionTest.ql b/java/ql/test/query-tests/security/CWE-441/UnsafeContentUriResolutionTest.ql index b087c0c1010..4a01079b53c 100644 --- a/java/ql/test/query-tests/security/CWE-441/UnsafeContentUriResolutionTest.ql +++ b/java/ql/test/query-tests/security/CWE-441/UnsafeContentUriResolutionTest.ql @@ -2,6 +2,10 @@ import java import TestUtilities.InlineFlowTest import semmle.code.java.security.UnsafeContentUriResolutionQuery +class EnableLegacy extends EnableLegacyConfiguration { + EnableLegacy() { exists(this) } +} + class Test extends InlineFlowTest { override DataFlow::Configuration getValueFlowConfig() { none() } diff --git a/java/ql/test/query-tests/security/CWE-470/FragmentInjectionTest.ql b/java/ql/test/query-tests/security/CWE-470/FragmentInjectionTest.ql index 78a7cd96a33..a7e13d87d23 100644 --- a/java/ql/test/query-tests/security/CWE-470/FragmentInjectionTest.ql +++ b/java/ql/test/query-tests/security/CWE-470/FragmentInjectionTest.ql @@ -2,6 +2,10 @@ import java import semmle.code.java.security.FragmentInjectionQuery import TestUtilities.InlineFlowTest +class EnableLegacy extends EnableLegacyConfiguration { + EnableLegacy() { exists(this) } +} + class Test extends InlineFlowTest { override DataFlow::Configuration getValueFlowConfig() { none() } diff --git a/java/ql/test/query-tests/security/CWE-489/webview-debugging/WebviewDebuggingEnabled.ql b/java/ql/test/query-tests/security/CWE-489/webview-debugging/WebviewDebuggingEnabled.ql index 2d390a22d9f..aeceb941e09 100644 --- a/java/ql/test/query-tests/security/CWE-489/webview-debugging/WebviewDebuggingEnabled.ql +++ b/java/ql/test/query-tests/security/CWE-489/webview-debugging/WebviewDebuggingEnabled.ql @@ -2,6 +2,10 @@ import java import TestUtilities.InlineFlowTest import semmle.code.java.security.WebviewDubuggingEnabledQuery +class EnableLegacy extends EnableLegacyConfiguration { + EnableLegacy() { exists(this) } +} + class HasFlowTest extends InlineFlowTest { override DataFlow::Configuration getTaintFlowConfig() { none() } diff --git a/java/ql/test/query-tests/security/CWE-532/SensitiveLogInfo.ql b/java/ql/test/query-tests/security/CWE-532/SensitiveLogInfo.ql index aac128a263d..02cdfacc56f 100644 --- a/java/ql/test/query-tests/security/CWE-532/SensitiveLogInfo.ql +++ b/java/ql/test/query-tests/security/CWE-532/SensitiveLogInfo.ql @@ -2,6 +2,10 @@ import java import TestUtilities.InlineFlowTest import semmle.code.java.security.SensitiveLoggingQuery +class EnableLegacy extends EnableLegacyConfiguration { + EnableLegacy() { exists(this) } +} + class HasFlowTest extends InlineFlowTest { override DataFlow::Configuration getTaintFlowConfig() { result instanceof SensitiveLoggerConfiguration diff --git a/java/ql/test/query-tests/security/CWE-780/RsaWithoutOaepTest.ql b/java/ql/test/query-tests/security/CWE-780/RsaWithoutOaepTest.ql index bf8e8cbae21..67d1f2ff862 100644 --- a/java/ql/test/query-tests/security/CWE-780/RsaWithoutOaepTest.ql +++ b/java/ql/test/query-tests/security/CWE-780/RsaWithoutOaepTest.ql @@ -3,6 +3,10 @@ import TestUtilities.InlineExpectationsTest import TestUtilities.InlineFlowTest import semmle.code.java.security.RsaWithoutOaepQuery +class EnableLegacy extends EnableLegacyConfiguration { + EnableLegacy() { exists(this) } +} + class HasFlowTest extends InlineFlowTest { override DataFlow::Configuration getTaintFlowConfig() { result instanceof RsaWithoutOaepConfig } diff --git a/java/ql/test/experimental/query-tests/security/CWE-321/HardcodedJwtKey.java b/java/ql/test/query-tests/security/CWE-798/semmle/tests/HardcodedJwtKey.java similarity index 53% rename from java/ql/test/experimental/query-tests/security/CWE-321/HardcodedJwtKey.java rename to java/ql/test/query-tests/security/CWE-798/semmle/tests/HardcodedJwtKey.java index cfcb027755b..c4b0b1914eb 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-321/HardcodedJwtKey.java +++ b/java/ql/test/query-tests/security/CWE-798/semmle/tests/HardcodedJwtKey.java @@ -16,7 +16,7 @@ public class HardcodedJwtKey { // BAD: Get secret from hardcoded string then sign a JWT token public String accessTokenBad(String username) { - Algorithm algorithm = Algorithm.HMAC256(SECRET); + Algorithm algorithm = Algorithm.HMAC256(SECRET); // $ HardcodedCredentialsApiCall return JWT.create() .withExpiresAt(new Date(new Date().getTime() + ACCESS_EXPIRE_TIME)) @@ -39,7 +39,7 @@ public class HardcodedJwtKey { // BAD: Get secret from hardcoded string then verify a JWT token public boolean verifyTokenBad(String token) { - JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)) + JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)) // $ HardcodedCredentialsApiCall .withIssuer(ISSUER) .build(); try { @@ -62,4 +62,49 @@ public class HardcodedJwtKey { return false; } } + + public String accessTokenBad384(String username) { + Algorithm algorithm = Algorithm.HMAC384(SECRET); // $ HardcodedCredentialsApiCall + + return JWT.create() + .withExpiresAt(new Date(new Date().getTime() + ACCESS_EXPIRE_TIME)) + .withIssuer(ISSUER) + .withClaim("username", username) + .sign(algorithm); + } + + // GOOD: Get secret from system configuration then sign a token + public String accessTokenGood384(String username) { + String tokenSecret = System.getenv("SECRET_KEY"); + Algorithm algorithm = Algorithm.HMAC384(tokenSecret); + + return JWT.create() + .withExpiresAt(new Date(new Date().getTime() + ACCESS_EXPIRE_TIME)) + .withIssuer(ISSUER) + .withClaim("username", username) + .sign(algorithm); + } + + public String accessTokenBad512(String username) { + Algorithm algorithm = Algorithm.HMAC512(SECRET); // $ HardcodedCredentialsApiCall + + return JWT.create() + .withExpiresAt(new Date(new Date().getTime() + ACCESS_EXPIRE_TIME)) + .withIssuer(ISSUER) + .withClaim("username", username) + .sign(algorithm); + } + + // GOOD: Get secret from system configuration then sign a token + public String accessTokenGood512(String username) { + String tokenSecret = System.getenv("SECRET_KEY"); + Algorithm algorithm = Algorithm.HMAC512(tokenSecret); + + return JWT.create() + .withExpiresAt(new Date(new Date().getTime() + ACCESS_EXPIRE_TIME)) + .withIssuer(ISSUER) + .withClaim("username", username) + .sign(algorithm); + } + } diff --git a/java/ql/test/query-tests/security/CWE-798/semmle/tests/options b/java/ql/test/query-tests/security/CWE-798/semmle/tests/options index 215e0929c43..18ff7ebd8a9 100644 --- a/java/ql/test/query-tests/security/CWE-798/semmle/tests/options +++ b/java/ql/test/query-tests/security/CWE-798/semmle/tests/options @@ -1 +1 @@ -// semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/amazon-aws-sdk-1.11.700:${testdir}/../../../../../stubs/azure-sdk-for-java:${testdir}/../../../../../stubs/shiro-core-1.4.0:${testdir}/../../../../../stubs/jsch-0.1.55:${testdir}/../../../../../stubs/ganymed-ssh-2-260:${testdir}/../../../../../stubs/apache-mina-sshd-2.8.0:${testdir}/../../../../../stubs/sshj-0.33.0:${testdir}/../../../../../stubs/j2ssh-1.5.5:${testdir}/../../../../../stubs/trilead-ssh2-212:${testdir}/../../../../../stubs/apache-commons-net-3.8.0:${testdir}/../../../../../stubs/mongodbClient:${testdir}/../../../../../stubs/mssql-jdbc-12.2.0 +// semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/amazon-aws-sdk-1.11.700:${testdir}/../../../../../stubs/azure-sdk-for-java:${testdir}/../../../../../stubs/shiro-core-1.4.0:${testdir}/../../../../../stubs/jsch-0.1.55:${testdir}/../../../../../stubs/ganymed-ssh-2-260:${testdir}/../../../../../stubs/apache-mina-sshd-2.8.0:${testdir}/../../../../../stubs/sshj-0.33.0:${testdir}/../../../../../stubs/j2ssh-1.5.5:${testdir}/../../../../../stubs/trilead-ssh2-212:${testdir}/../../../../../stubs/apache-commons-net-3.8.0:${testdir}/../../../../../stubs/mongodbClient:${testdir}/../../../../../stubs/mssql-jdbc-12.2.0:${testdir}/../../../../../stubs/auth0-jwt-2.3 diff --git a/java/ql/test/query-tests/security/CWE-927/SensitiveCommunication.ql b/java/ql/test/query-tests/security/CWE-927/SensitiveCommunication.ql index ae632646c96..40f74b0e911 100644 --- a/java/ql/test/query-tests/security/CWE-927/SensitiveCommunication.ql +++ b/java/ql/test/query-tests/security/CWE-927/SensitiveCommunication.ql @@ -3,6 +3,10 @@ import semmle.code.java.security.AndroidSensitiveCommunicationQuery import TestUtilities.InlineExpectationsTest import TestUtilities.InlineFlowTest +class EnableLegacy extends EnableLegacyConfiguration { + EnableLegacy() { exists(this) } +} + class HasFlowTest extends InlineFlowTest { override DataFlow::Configuration getTaintFlowConfig() { result = any(SensitiveCommunicationConfig c) diff --git a/java/ql/test/stubs/apache-hive/com/google/protobuf/AbstractMessage.java b/java/ql/test/stubs/apache-hive/com/google/protobuf/AbstractMessage.java new file mode 100644 index 00000000000..80f8f3f8c8b --- /dev/null +++ b/java/ql/test/stubs/apache-hive/com/google/protobuf/AbstractMessage.java @@ -0,0 +1,59 @@ +// Generated automatically from com.google.protobuf.AbstractMessage for testing purposes + +package com.google.protobuf; + +import com.google.protobuf.AbstractMessageLite; +import com.google.protobuf.ByteString; +import com.google.protobuf.CodedInputStream; +import com.google.protobuf.CodedOutputStream; +import com.google.protobuf.Descriptors; +import com.google.protobuf.ExtensionRegistryLite; +import com.google.protobuf.Internal; +import com.google.protobuf.Message; +import com.google.protobuf.UninitializedMessageException; +import com.google.protobuf.UnknownFieldSet; +import java.io.InputStream; +import java.util.List; +import java.util.Map; + +abstract public class AbstractMessage extends AbstractMessageLite implements Message +{ + abstract static public class Builder extends AbstractMessageLite.Builder implements Message.Builder + { + protected static UninitializedMessageException newUninitializedMessageException(Message p0){ return null; } + public Builder(){} + public BuilderType clear(){ return null; } + public BuilderType mergeFrom(ByteString p0){ return null; } + public BuilderType mergeFrom(ByteString p0, ExtensionRegistryLite p1){ return null; } + public BuilderType mergeFrom(CodedInputStream p0){ return null; } + public BuilderType mergeFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public BuilderType mergeFrom(InputStream p0){ return null; } + public BuilderType mergeFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public BuilderType mergeFrom(Message p0){ return null; } + public BuilderType mergeFrom(byte[] p0){ return null; } + public BuilderType mergeFrom(byte[] p0, ExtensionRegistryLite p1){ return null; } + public BuilderType mergeFrom(byte[] p0, int p1, int p2){ return null; } + public BuilderType mergeFrom(byte[] p0, int p1, int p2, ExtensionRegistryLite p3){ return null; } + public BuilderType mergeUnknownFields(UnknownFieldSet p0){ return null; } + public List findInitializationErrors(){ return null; } + public Message.Builder getFieldBuilder(Descriptors.FieldDescriptor p0){ return null; } + public String getInitializationErrorString(){ return null; } + public abstract BuilderType clone(); + public boolean mergeDelimitedFrom(InputStream p0){ return false; } + public boolean mergeDelimitedFrom(InputStream p0, ExtensionRegistryLite p1){ return false; } + } + protected int hashFields(int p0, Map p1){ return 0; } + protected static int hashBoolean(boolean p0){ return 0; } + protected static int hashEnum(Internal.EnumLite p0){ return 0; } + protected static int hashEnumList(List p0){ return 0; } + protected static int hashLong(long p0){ return 0; } + public AbstractMessage(){} + public List findInitializationErrors(){ return null; } + public String getInitializationErrorString(){ return null; } + public boolean equals(Object p0){ return false; } + public boolean isInitialized(){ return false; } + public final String toString(){ return null; } + public int getSerializedSize(){ return 0; } + public int hashCode(){ return 0; } + public void writeTo(CodedOutputStream p0){} +} diff --git a/java/ql/test/stubs/apache-hive/com/google/protobuf/AbstractMessageLite.java b/java/ql/test/stubs/apache-hive/com/google/protobuf/AbstractMessageLite.java new file mode 100644 index 00000000000..54edf8bf01e --- /dev/null +++ b/java/ql/test/stubs/apache-hive/com/google/protobuf/AbstractMessageLite.java @@ -0,0 +1,40 @@ +// Generated automatically from com.google.protobuf.AbstractMessageLite for testing purposes + +package com.google.protobuf; + +import com.google.protobuf.ByteString; +import com.google.protobuf.CodedInputStream; +import com.google.protobuf.ExtensionRegistryLite; +import com.google.protobuf.MessageLite; +import com.google.protobuf.UninitializedMessageException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collection; + +abstract public class AbstractMessageLite implements MessageLite +{ + abstract static public class Builder implements MessageLite.Builder + { + protected static void addAll(java.lang.Iterable p0, Collection p1){} + protected static UninitializedMessageException newUninitializedMessageException(MessageLite p0){ return null; } + public Builder(){} + public BuilderType mergeFrom(ByteString p0){ return null; } + public BuilderType mergeFrom(ByteString p0, ExtensionRegistryLite p1){ return null; } + public BuilderType mergeFrom(CodedInputStream p0){ return null; } + public BuilderType mergeFrom(InputStream p0){ return null; } + public BuilderType mergeFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public BuilderType mergeFrom(byte[] p0){ return null; } + public BuilderType mergeFrom(byte[] p0, ExtensionRegistryLite p1){ return null; } + public BuilderType mergeFrom(byte[] p0, int p1, int p2){ return null; } + public BuilderType mergeFrom(byte[] p0, int p1, int p2, ExtensionRegistryLite p3){ return null; } + public abstract BuilderType clone(); + public abstract BuilderType mergeFrom(CodedInputStream p0, ExtensionRegistryLite p1); + public boolean mergeDelimitedFrom(InputStream p0){ return false; } + public boolean mergeDelimitedFrom(InputStream p0, ExtensionRegistryLite p1){ return false; } + } + public AbstractMessageLite(){} + public ByteString toByteString(){ return null; } + public byte[] toByteArray(){ return null; } + public void writeDelimitedTo(OutputStream p0){} + public void writeTo(OutputStream p0){} +} diff --git a/java/ql/test/stubs/apache-hive/com/google/protobuf/AbstractParser.java b/java/ql/test/stubs/apache-hive/com/google/protobuf/AbstractParser.java new file mode 100644 index 00000000000..a0195ccd629 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/com/google/protobuf/AbstractParser.java @@ -0,0 +1,38 @@ +// Generated automatically from com.google.protobuf.AbstractParser for testing purposes + +package com.google.protobuf; + +import com.google.protobuf.ByteString; +import com.google.protobuf.CodedInputStream; +import com.google.protobuf.ExtensionRegistryLite; +import com.google.protobuf.MessageLite; +import com.google.protobuf.Parser; +import java.io.InputStream; + +abstract public class AbstractParser implements Parser +{ + public AbstractParser(){} + public MessageType parseDelimitedFrom(InputStream p0){ return null; } + public MessageType parseDelimitedFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public MessageType parseFrom(ByteString p0){ return null; } + public MessageType parseFrom(ByteString p0, ExtensionRegistryLite p1){ return null; } + public MessageType parseFrom(CodedInputStream p0){ return null; } + public MessageType parseFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public MessageType parseFrom(InputStream p0){ return null; } + public MessageType parseFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public MessageType parseFrom(byte[] p0){ return null; } + public MessageType parseFrom(byte[] p0, ExtensionRegistryLite p1){ return null; } + public MessageType parseFrom(byte[] p0, int p1, int p2){ return null; } + public MessageType parseFrom(byte[] p0, int p1, int p2, ExtensionRegistryLite p3){ return null; } + public MessageType parsePartialDelimitedFrom(InputStream p0){ return null; } + public MessageType parsePartialDelimitedFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public MessageType parsePartialFrom(ByteString p0){ return null; } + public MessageType parsePartialFrom(ByteString p0, ExtensionRegistryLite p1){ return null; } + public MessageType parsePartialFrom(CodedInputStream p0){ return null; } + public MessageType parsePartialFrom(InputStream p0){ return null; } + public MessageType parsePartialFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public MessageType parsePartialFrom(byte[] p0){ return null; } + public MessageType parsePartialFrom(byte[] p0, ExtensionRegistryLite p1){ return null; } + public MessageType parsePartialFrom(byte[] p0, int p1, int p2){ return null; } + public MessageType parsePartialFrom(byte[] p0, int p1, int p2, ExtensionRegistryLite p3){ return null; } +} diff --git a/java/ql/test/stubs/apache-hive/com/google/protobuf/ByteString.java b/java/ql/test/stubs/apache-hive/com/google/protobuf/ByteString.java new file mode 100644 index 00000000000..62ac8028704 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/com/google/protobuf/ByteString.java @@ -0,0 +1,72 @@ +// Generated automatically from com.google.protobuf.ByteString for testing purposes + +package com.google.protobuf; + +import com.google.protobuf.CodedInputStream; +import com.google.protobuf.CodedOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.Iterator; +import java.util.List; + +abstract public class ByteString implements Iterable +{ + protected abstract boolean isBalanced(); + protected abstract int getTreeDepth(); + protected abstract int partialHash(int p0, int p1, int p2); + protected abstract int partialIsValidUtf8(int p0, int p1, int p2); + protected abstract int peekCachedHashCode(); + protected abstract void copyToInternal(byte[] p0, int p1, int p2, int p3); + public ByteString concat(ByteString p0){ return null; } + public ByteString substring(int p0){ return null; } + public String toString(){ return null; } + public String toStringUtf8(){ return null; } + public abstract ByteBuffer asReadOnlyByteBuffer(); + public abstract ByteString substring(int p0, int p1); + public abstract ByteString.ByteIterator iterator(); + public abstract CodedInputStream newCodedInput(); + public abstract InputStream newInput(); + public abstract List asReadOnlyByteBufferList(); + public abstract String toString(String p0); + public abstract boolean equals(Object p0); + public abstract boolean isValidUtf8(); + public abstract byte byteAt(int p0); + public abstract int hashCode(); + public abstract int size(); + public abstract void copyTo(ByteBuffer p0); + public abstract void writeTo(OutputStream p0); + public boolean isEmpty(){ return false; } + public boolean startsWith(ByteString p0){ return false; } + public byte[] toByteArray(){ return null; } + public static ByteString EMPTY = null; + public static ByteString copyFrom(ByteBuffer p0){ return null; } + public static ByteString copyFrom(ByteBuffer p0, int p1){ return null; } + public static ByteString copyFrom(Iterable p0){ return null; } + public static ByteString copyFrom(String p0, String p1){ return null; } + public static ByteString copyFrom(byte[] p0){ return null; } + public static ByteString copyFrom(byte[] p0, int p1, int p2){ return null; } + public static ByteString copyFromUtf8(String p0){ return null; } + public static ByteString readFrom(InputStream p0){ return null; } + public static ByteString readFrom(InputStream p0, int p1){ return null; } + public static ByteString readFrom(InputStream p0, int p1, int p2){ return null; } + public static ByteString.Output newOutput(){ return null; } + public static ByteString.Output newOutput(int p0){ return null; } + public void copyTo(byte[] p0, int p1){} + public void copyTo(byte[] p0, int p1, int p2, int p3){} + static public class Output extends OutputStream + { + protected Output() {} + public ByteString toByteString(){ return null; } + public String toString(){ return null; } + public int size(){ return 0; } + public void reset(){} + public void write(byte[] p0, int p1, int p2){} + public void write(int p0){} + public void writeTo(OutputStream p0){} + } + static public interface ByteIterator extends Iterator + { + byte nextByte(); + } +} diff --git a/java/ql/test/stubs/apache-hive/com/google/protobuf/CodedInputStream.java b/java/ql/test/stubs/apache-hive/com/google/protobuf/CodedInputStream.java new file mode 100644 index 00000000000..d4b60c6245b --- /dev/null +++ b/java/ql/test/stubs/apache-hive/com/google/protobuf/CodedInputStream.java @@ -0,0 +1,60 @@ +// Generated automatically from com.google.protobuf.CodedInputStream for testing purposes + +package com.google.protobuf; + +import com.google.protobuf.ByteString; +import com.google.protobuf.ExtensionRegistryLite; +import com.google.protobuf.MessageLite; +import com.google.protobuf.Parser; +import java.io.InputStream; + +public class CodedInputStream +{ + protected CodedInputStream() {} + public T readGroup(int p0, com.google.protobuf.Parser p1, ExtensionRegistryLite p2){ return null; } + public T readMessage(com.google.protobuf.Parser p0, ExtensionRegistryLite p1){ return null; } + public ByteString readBytes(){ return null; } + public String readString(){ return null; } + public boolean isAtEnd(){ return false; } + public boolean readBool(){ return false; } + public boolean skipField(int p0){ return false; } + public byte readRawByte(){ return 0; } + public byte[] readRawBytes(int p0){ return null; } + public double readDouble(){ return 0; } + public float readFloat(){ return 0; } + public int getBytesUntilLimit(){ return 0; } + public int getTotalBytesRead(){ return 0; } + public int pushLimit(int p0){ return 0; } + public int readEnum(){ return 0; } + public int readFixed32(){ return 0; } + public int readInt32(){ return 0; } + public int readRawLittleEndian32(){ return 0; } + public int readRawVarint32(){ return 0; } + public int readSFixed32(){ return 0; } + public int readSInt32(){ return 0; } + public int readTag(){ return 0; } + public int readUInt32(){ return 0; } + public int setRecursionLimit(int p0){ return 0; } + public int setSizeLimit(int p0){ return 0; } + public long readFixed64(){ return 0; } + public long readInt64(){ return 0; } + public long readRawLittleEndian64(){ return 0; } + public long readRawVarint64(){ return 0; } + public long readSFixed64(){ return 0; } + public long readSInt64(){ return 0; } + public long readUInt64(){ return 0; } + public static CodedInputStream newInstance(InputStream p0){ return null; } + public static CodedInputStream newInstance(byte[] p0){ return null; } + public static CodedInputStream newInstance(byte[] p0, int p1, int p2){ return null; } + public static int decodeZigZag32(int p0){ return 0; } + public static int readRawVarint32(int p0, InputStream p1){ return 0; } + public static long decodeZigZag64(long p0){ return 0; } + public void checkLastTagWas(int p0){} + public void popLimit(int p0){} + public void readGroup(int p0, MessageLite.Builder p1, ExtensionRegistryLite p2){} + public void readMessage(MessageLite.Builder p0, ExtensionRegistryLite p1){} + public void readUnknownGroup(int p0, MessageLite.Builder p1){} + public void resetSizeCounter(){} + public void skipMessage(){} + public void skipRawBytes(int p0){} +} diff --git a/java/ql/test/stubs/apache-hive/com/google/protobuf/CodedOutputStream.java b/java/ql/test/stubs/apache-hive/com/google/protobuf/CodedOutputStream.java new file mode 100644 index 00000000000..315b7bac524 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/com/google/protobuf/CodedOutputStream.java @@ -0,0 +1,122 @@ +// Generated automatically from com.google.protobuf.CodedOutputStream for testing purposes + +package com.google.protobuf; + +import com.google.protobuf.ByteString; +import com.google.protobuf.LazyField; +import com.google.protobuf.MessageLite; +import java.io.OutputStream; + +public class CodedOutputStream +{ + protected CodedOutputStream() {} + public int spaceLeft(){ return 0; } + public static CodedOutputStream newInstance(OutputStream p0){ return null; } + public static CodedOutputStream newInstance(OutputStream p0, int p1){ return null; } + public static CodedOutputStream newInstance(byte[] p0){ return null; } + public static CodedOutputStream newInstance(byte[] p0, int p1, int p2){ return null; } + public static int DEFAULT_BUFFER_SIZE = 0; + public static int LITTLE_ENDIAN_32_SIZE = 0; + public static int LITTLE_ENDIAN_64_SIZE = 0; + public static int computeBoolSize(int p0, boolean p1){ return 0; } + public static int computeBoolSizeNoTag(boolean p0){ return 0; } + public static int computeBytesSize(int p0, ByteString p1){ return 0; } + public static int computeBytesSizeNoTag(ByteString p0){ return 0; } + public static int computeDoubleSize(int p0, double p1){ return 0; } + public static int computeDoubleSizeNoTag(double p0){ return 0; } + public static int computeEnumSize(int p0, int p1){ return 0; } + public static int computeEnumSizeNoTag(int p0){ return 0; } + public static int computeFixed32Size(int p0, int p1){ return 0; } + public static int computeFixed32SizeNoTag(int p0){ return 0; } + public static int computeFixed64Size(int p0, long p1){ return 0; } + public static int computeFixed64SizeNoTag(long p0){ return 0; } + public static int computeFloatSize(int p0, float p1){ return 0; } + public static int computeFloatSizeNoTag(float p0){ return 0; } + public static int computeGroupSize(int p0, MessageLite p1){ return 0; } + public static int computeGroupSizeNoTag(MessageLite p0){ return 0; } + public static int computeInt32Size(int p0, int p1){ return 0; } + public static int computeInt32SizeNoTag(int p0){ return 0; } + public static int computeInt64Size(int p0, long p1){ return 0; } + public static int computeInt64SizeNoTag(long p0){ return 0; } + public static int computeLazyFieldMessageSetExtensionSize(int p0, LazyField p1){ return 0; } + public static int computeLazyFieldSize(int p0, LazyField p1){ return 0; } + public static int computeLazyFieldSizeNoTag(LazyField p0){ return 0; } + public static int computeMessageSetExtensionSize(int p0, MessageLite p1){ return 0; } + public static int computeMessageSize(int p0, MessageLite p1){ return 0; } + public static int computeMessageSizeNoTag(MessageLite p0){ return 0; } + public static int computeRawMessageSetExtensionSize(int p0, ByteString p1){ return 0; } + public static int computeRawVarint32Size(int p0){ return 0; } + public static int computeRawVarint64Size(long p0){ return 0; } + public static int computeSFixed32Size(int p0, int p1){ return 0; } + public static int computeSFixed32SizeNoTag(int p0){ return 0; } + public static int computeSFixed64Size(int p0, long p1){ return 0; } + public static int computeSFixed64SizeNoTag(long p0){ return 0; } + public static int computeSInt32Size(int p0, int p1){ return 0; } + public static int computeSInt32SizeNoTag(int p0){ return 0; } + public static int computeSInt64Size(int p0, long p1){ return 0; } + public static int computeSInt64SizeNoTag(long p0){ return 0; } + public static int computeStringSize(int p0, String p1){ return 0; } + public static int computeStringSizeNoTag(String p0){ return 0; } + public static int computeTagSize(int p0){ return 0; } + public static int computeUInt32Size(int p0, int p1){ return 0; } + public static int computeUInt32SizeNoTag(int p0){ return 0; } + public static int computeUInt64Size(int p0, long p1){ return 0; } + public static int computeUInt64SizeNoTag(long p0){ return 0; } + public static int computeUnknownGroupSize(int p0, MessageLite p1){ return 0; } + public static int computeUnknownGroupSizeNoTag(MessageLite p0){ return 0; } + public static int encodeZigZag32(int p0){ return 0; } + public static long encodeZigZag64(long p0){ return 0; } + public void checkNoSpaceLeft(){} + public void flush(){} + public void writeBool(int p0, boolean p1){} + public void writeBoolNoTag(boolean p0){} + public void writeBytes(int p0, ByteString p1){} + public void writeBytesNoTag(ByteString p0){} + public void writeDouble(int p0, double p1){} + public void writeDoubleNoTag(double p0){} + public void writeEnum(int p0, int p1){} + public void writeEnumNoTag(int p0){} + public void writeFixed32(int p0, int p1){} + public void writeFixed32NoTag(int p0){} + public void writeFixed64(int p0, long p1){} + public void writeFixed64NoTag(long p0){} + public void writeFloat(int p0, float p1){} + public void writeFloatNoTag(float p0){} + public void writeGroup(int p0, MessageLite p1){} + public void writeGroupNoTag(MessageLite p0){} + public void writeInt32(int p0, int p1){} + public void writeInt32NoTag(int p0){} + public void writeInt64(int p0, long p1){} + public void writeInt64NoTag(long p0){} + public void writeMessage(int p0, MessageLite p1){} + public void writeMessageNoTag(MessageLite p0){} + public void writeMessageSetExtension(int p0, MessageLite p1){} + public void writeRawByte(byte p0){} + public void writeRawByte(int p0){} + public void writeRawBytes(ByteString p0){} + public void writeRawBytes(ByteString p0, int p1, int p2){} + public void writeRawBytes(byte[] p0){} + public void writeRawBytes(byte[] p0, int p1, int p2){} + public void writeRawLittleEndian32(int p0){} + public void writeRawLittleEndian64(long p0){} + public void writeRawMessageSetExtension(int p0, ByteString p1){} + public void writeRawVarint32(int p0){} + public void writeRawVarint64(long p0){} + public void writeSFixed32(int p0, int p1){} + public void writeSFixed32NoTag(int p0){} + public void writeSFixed64(int p0, long p1){} + public void writeSFixed64NoTag(long p0){} + public void writeSInt32(int p0, int p1){} + public void writeSInt32NoTag(int p0){} + public void writeSInt64(int p0, long p1){} + public void writeSInt64NoTag(long p0){} + public void writeString(int p0, String p1){} + public void writeStringNoTag(String p0){} + public void writeTag(int p0, int p1){} + public void writeUInt32(int p0, int p1){} + public void writeUInt32NoTag(int p0){} + public void writeUInt64(int p0, long p1){} + public void writeUInt64NoTag(long p0){} + public void writeUnknownGroup(int p0, MessageLite p1){} + public void writeUnknownGroupNoTag(MessageLite p0){} +} diff --git a/java/ql/test/stubs/apache-hive/com/google/protobuf/DescriptorProtos.java b/java/ql/test/stubs/apache-hive/com/google/protobuf/DescriptorProtos.java new file mode 100644 index 00000000000..3ba998ea3e3 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/com/google/protobuf/DescriptorProtos.java @@ -0,0 +1,2308 @@ +// Generated automatically from com.google.protobuf.DescriptorProtos for testing purposes + +package com.google.protobuf; + +import com.google.protobuf.ByteString; +import com.google.protobuf.CodedInputStream; +import com.google.protobuf.CodedOutputStream; +import com.google.protobuf.Descriptors; +import com.google.protobuf.ExtensionRegistry; +import com.google.protobuf.ExtensionRegistryLite; +import com.google.protobuf.GeneratedMessage; +import com.google.protobuf.Internal; +import com.google.protobuf.Message; +import com.google.protobuf.MessageOrBuilder; +import com.google.protobuf.Parser; +import com.google.protobuf.UnknownFieldSet; +import java.io.InputStream; +import java.util.List; + +public class DescriptorProtos +{ + protected DescriptorProtos() {} + public static Descriptors.FileDescriptor getDescriptor(){ return null; } + public static void registerAllExtensions(ExtensionRegistry p0){} + static public class DescriptorProto extends GeneratedMessage implements DescriptorProtos.DescriptorProtoOrBuilder + { + protected DescriptorProto() {} + protected DescriptorProtos.DescriptorProto.Builder newBuilderForType(GeneratedMessage.BuilderParent p0){ return null; } + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + protected Object writeReplace(){ return null; } + public ByteString getNameBytes(){ return null; } + public DescriptorProtos.DescriptorProto getDefaultInstanceForType(){ return null; } + public DescriptorProtos.DescriptorProto getNestedType(int p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder newBuilderForType(){ return null; } + public DescriptorProtos.DescriptorProto.Builder toBuilder(){ return null; } + public DescriptorProtos.DescriptorProto.ExtensionRange getExtensionRange(int p0){ return null; } + public DescriptorProtos.DescriptorProto.ExtensionRangeOrBuilder getExtensionRangeOrBuilder(int p0){ return null; } + public DescriptorProtos.DescriptorProtoOrBuilder getNestedTypeOrBuilder(int p0){ return null; } + public DescriptorProtos.EnumDescriptorProto getEnumType(int p0){ return null; } + public DescriptorProtos.EnumDescriptorProtoOrBuilder getEnumTypeOrBuilder(int p0){ return null; } + public DescriptorProtos.FieldDescriptorProto getExtension(int p0){ return null; } + public DescriptorProtos.FieldDescriptorProto getField(int p0){ return null; } + public DescriptorProtos.FieldDescriptorProtoOrBuilder getExtensionOrBuilder(int p0){ return null; } + public DescriptorProtos.FieldDescriptorProtoOrBuilder getFieldOrBuilder(int p0){ return null; } + public DescriptorProtos.MessageOptions getOptions(){ return null; } + public DescriptorProtos.MessageOptionsOrBuilder getOptionsOrBuilder(){ return null; } + public List getExtensionRangeOrBuilderList(){ return null; } + public List getNestedTypeOrBuilderList(){ return null; } + public List getEnumTypeOrBuilderList(){ return null; } + public List getExtensionOrBuilderList(){ return null; } + public List getFieldOrBuilderList(){ return null; } + public List getExtensionRangeList(){ return null; } + public List getNestedTypeList(){ return null; } + public List getEnumTypeList(){ return null; } + public List getExtensionList(){ return null; } + public List getFieldList(){ return null; } + public Parser getParserForType(){ return null; } + public String getName(){ return null; } + public boolean hasName(){ return false; } + public boolean hasOptions(){ return false; } + public final UnknownFieldSet getUnknownFields(){ return null; } + public final boolean isInitialized(){ return false; } + public int getEnumTypeCount(){ return 0; } + public int getExtensionCount(){ return 0; } + public int getExtensionRangeCount(){ return 0; } + public int getFieldCount(){ return 0; } + public int getNestedTypeCount(){ return 0; } + public int getSerializedSize(){ return 0; } + public static DescriptorProtos.DescriptorProto getDefaultInstance(){ return null; } + public static DescriptorProtos.DescriptorProto parseDelimitedFrom(InputStream p0){ return null; } + public static DescriptorProtos.DescriptorProto parseDelimitedFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.DescriptorProto parseFrom(ByteString p0){ return null; } + public static DescriptorProtos.DescriptorProto parseFrom(ByteString p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.DescriptorProto parseFrom(CodedInputStream p0){ return null; } + public static DescriptorProtos.DescriptorProto parseFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.DescriptorProto parseFrom(InputStream p0){ return null; } + public static DescriptorProtos.DescriptorProto parseFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.DescriptorProto parseFrom(byte[] p0){ return null; } + public static DescriptorProtos.DescriptorProto parseFrom(byte[] p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.DescriptorProto.Builder newBuilder(){ return null; } + public static DescriptorProtos.DescriptorProto.Builder newBuilder(DescriptorProtos.DescriptorProto p0){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + public static Parser PARSER = null; + public static int ENUM_TYPE_FIELD_NUMBER = 0; + public static int EXTENSION_FIELD_NUMBER = 0; + public static int EXTENSION_RANGE_FIELD_NUMBER = 0; + public static int FIELD_FIELD_NUMBER = 0; + public static int NAME_FIELD_NUMBER = 0; + public static int NESTED_TYPE_FIELD_NUMBER = 0; + public static int OPTIONS_FIELD_NUMBER = 0; + public void writeTo(CodedOutputStream p0){} + static public class Builder extends GeneratedMessage.Builder implements DescriptorProtos.DescriptorProtoOrBuilder + { + protected Builder() {} + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + public ByteString getNameBytes(){ return null; } + public DescriptorProtos.DescriptorProto build(){ return null; } + public DescriptorProtos.DescriptorProto buildPartial(){ return null; } + public DescriptorProtos.DescriptorProto getDefaultInstanceForType(){ return null; } + public DescriptorProtos.DescriptorProto getNestedType(int p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder addAllEnumType(Iterable p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder addAllExtension(Iterable p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder addAllExtensionRange(Iterable p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder addAllField(Iterable p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder addAllNestedType(Iterable p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder addEnumType(DescriptorProtos.EnumDescriptorProto p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder addEnumType(DescriptorProtos.EnumDescriptorProto.Builder p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder addEnumType(int p0, DescriptorProtos.EnumDescriptorProto p1){ return null; } + public DescriptorProtos.DescriptorProto.Builder addEnumType(int p0, DescriptorProtos.EnumDescriptorProto.Builder p1){ return null; } + public DescriptorProtos.DescriptorProto.Builder addExtension(DescriptorProtos.FieldDescriptorProto p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder addExtension(DescriptorProtos.FieldDescriptorProto.Builder p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder addExtension(int p0, DescriptorProtos.FieldDescriptorProto p1){ return null; } + public DescriptorProtos.DescriptorProto.Builder addExtension(int p0, DescriptorProtos.FieldDescriptorProto.Builder p1){ return null; } + public DescriptorProtos.DescriptorProto.Builder addExtensionRange(DescriptorProtos.DescriptorProto.ExtensionRange p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder addExtensionRange(DescriptorProtos.DescriptorProto.ExtensionRange.Builder p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder addExtensionRange(int p0, DescriptorProtos.DescriptorProto.ExtensionRange p1){ return null; } + public DescriptorProtos.DescriptorProto.Builder addExtensionRange(int p0, DescriptorProtos.DescriptorProto.ExtensionRange.Builder p1){ return null; } + public DescriptorProtos.DescriptorProto.Builder addField(DescriptorProtos.FieldDescriptorProto p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder addField(DescriptorProtos.FieldDescriptorProto.Builder p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder addField(int p0, DescriptorProtos.FieldDescriptorProto p1){ return null; } + public DescriptorProtos.DescriptorProto.Builder addField(int p0, DescriptorProtos.FieldDescriptorProto.Builder p1){ return null; } + public DescriptorProtos.DescriptorProto.Builder addNestedType(DescriptorProtos.DescriptorProto p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder addNestedType(DescriptorProtos.DescriptorProto.Builder p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder addNestedType(int p0, DescriptorProtos.DescriptorProto p1){ return null; } + public DescriptorProtos.DescriptorProto.Builder addNestedType(int p0, DescriptorProtos.DescriptorProto.Builder p1){ return null; } + public DescriptorProtos.DescriptorProto.Builder addNestedTypeBuilder(){ return null; } + public DescriptorProtos.DescriptorProto.Builder addNestedTypeBuilder(int p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder clear(){ return null; } + public DescriptorProtos.DescriptorProto.Builder clearEnumType(){ return null; } + public DescriptorProtos.DescriptorProto.Builder clearExtension(){ return null; } + public DescriptorProtos.DescriptorProto.Builder clearExtensionRange(){ return null; } + public DescriptorProtos.DescriptorProto.Builder clearField(){ return null; } + public DescriptorProtos.DescriptorProto.Builder clearName(){ return null; } + public DescriptorProtos.DescriptorProto.Builder clearNestedType(){ return null; } + public DescriptorProtos.DescriptorProto.Builder clearOptions(){ return null; } + public DescriptorProtos.DescriptorProto.Builder clone(){ return null; } + public DescriptorProtos.DescriptorProto.Builder getNestedTypeBuilder(int p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder mergeFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public DescriptorProtos.DescriptorProto.Builder mergeFrom(DescriptorProtos.DescriptorProto p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder mergeFrom(Message p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder mergeOptions(DescriptorProtos.MessageOptions p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder removeEnumType(int p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder removeExtension(int p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder removeExtensionRange(int p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder removeField(int p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder removeNestedType(int p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder setEnumType(int p0, DescriptorProtos.EnumDescriptorProto p1){ return null; } + public DescriptorProtos.DescriptorProto.Builder setEnumType(int p0, DescriptorProtos.EnumDescriptorProto.Builder p1){ return null; } + public DescriptorProtos.DescriptorProto.Builder setExtension(int p0, DescriptorProtos.FieldDescriptorProto p1){ return null; } + public DescriptorProtos.DescriptorProto.Builder setExtension(int p0, DescriptorProtos.FieldDescriptorProto.Builder p1){ return null; } + public DescriptorProtos.DescriptorProto.Builder setExtensionRange(int p0, DescriptorProtos.DescriptorProto.ExtensionRange p1){ return null; } + public DescriptorProtos.DescriptorProto.Builder setExtensionRange(int p0, DescriptorProtos.DescriptorProto.ExtensionRange.Builder p1){ return null; } + public DescriptorProtos.DescriptorProto.Builder setField(int p0, DescriptorProtos.FieldDescriptorProto p1){ return null; } + public DescriptorProtos.DescriptorProto.Builder setField(int p0, DescriptorProtos.FieldDescriptorProto.Builder p1){ return null; } + public DescriptorProtos.DescriptorProto.Builder setName(String p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder setNameBytes(ByteString p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder setNestedType(int p0, DescriptorProtos.DescriptorProto p1){ return null; } + public DescriptorProtos.DescriptorProto.Builder setNestedType(int p0, DescriptorProtos.DescriptorProto.Builder p1){ return null; } + public DescriptorProtos.DescriptorProto.Builder setOptions(DescriptorProtos.MessageOptions p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder setOptions(DescriptorProtos.MessageOptions.Builder p0){ return null; } + public DescriptorProtos.DescriptorProto.ExtensionRange getExtensionRange(int p0){ return null; } + public DescriptorProtos.DescriptorProto.ExtensionRange.Builder addExtensionRangeBuilder(){ return null; } + public DescriptorProtos.DescriptorProto.ExtensionRange.Builder addExtensionRangeBuilder(int p0){ return null; } + public DescriptorProtos.DescriptorProto.ExtensionRange.Builder getExtensionRangeBuilder(int p0){ return null; } + public DescriptorProtos.DescriptorProto.ExtensionRangeOrBuilder getExtensionRangeOrBuilder(int p0){ return null; } + public DescriptorProtos.DescriptorProtoOrBuilder getNestedTypeOrBuilder(int p0){ return null; } + public DescriptorProtos.EnumDescriptorProto getEnumType(int p0){ return null; } + public DescriptorProtos.EnumDescriptorProto.Builder addEnumTypeBuilder(){ return null; } + public DescriptorProtos.EnumDescriptorProto.Builder addEnumTypeBuilder(int p0){ return null; } + public DescriptorProtos.EnumDescriptorProto.Builder getEnumTypeBuilder(int p0){ return null; } + public DescriptorProtos.EnumDescriptorProtoOrBuilder getEnumTypeOrBuilder(int p0){ return null; } + public DescriptorProtos.FieldDescriptorProto getExtension(int p0){ return null; } + public DescriptorProtos.FieldDescriptorProto getField(int p0){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder addExtensionBuilder(){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder addExtensionBuilder(int p0){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder addFieldBuilder(){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder addFieldBuilder(int p0){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder getExtensionBuilder(int p0){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder getFieldBuilder(int p0){ return null; } + public DescriptorProtos.FieldDescriptorProtoOrBuilder getExtensionOrBuilder(int p0){ return null; } + public DescriptorProtos.FieldDescriptorProtoOrBuilder getFieldOrBuilder(int p0){ return null; } + public DescriptorProtos.MessageOptions getOptions(){ return null; } + public DescriptorProtos.MessageOptions.Builder getOptionsBuilder(){ return null; } + public DescriptorProtos.MessageOptionsOrBuilder getOptionsOrBuilder(){ return null; } + public Descriptors.Descriptor getDescriptorForType(){ return null; } + public List getExtensionRangeOrBuilderList(){ return null; } + public List getNestedTypeOrBuilderList(){ return null; } + public List getEnumTypeOrBuilderList(){ return null; } + public List getExtensionOrBuilderList(){ return null; } + public List getFieldOrBuilderList(){ return null; } + public List getExtensionRangeList(){ return null; } + public List getNestedTypeList(){ return null; } + public List getEnumTypeList(){ return null; } + public List getExtensionList(){ return null; } + public List getFieldList(){ return null; } + public String getName(){ return null; } + public boolean hasName(){ return false; } + public boolean hasOptions(){ return false; } + public final boolean isInitialized(){ return false; } + public int getEnumTypeCount(){ return 0; } + public int getExtensionCount(){ return 0; } + public int getExtensionRangeCount(){ return 0; } + public int getFieldCount(){ return 0; } + public int getNestedTypeCount(){ return 0; } + public java.util.List getNestedTypeBuilderList(){ return null; } + public java.util.List getExtensionRangeBuilderList(){ return null; } + public java.util.List getEnumTypeBuilderList(){ return null; } + public java.util.List getExtensionBuilderList(){ return null; } + public java.util.List getFieldBuilderList(){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + } + static public class ExtensionRange extends GeneratedMessage implements DescriptorProtos.DescriptorProto.ExtensionRangeOrBuilder + { + protected ExtensionRange() {} + protected DescriptorProtos.DescriptorProto.ExtensionRange.Builder newBuilderForType(GeneratedMessage.BuilderParent p0){ return null; } + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + protected Object writeReplace(){ return null; } + public DescriptorProtos.DescriptorProto.ExtensionRange getDefaultInstanceForType(){ return null; } + public DescriptorProtos.DescriptorProto.ExtensionRange.Builder newBuilderForType(){ return null; } + public DescriptorProtos.DescriptorProto.ExtensionRange.Builder toBuilder(){ return null; } + public Parser getParserForType(){ return null; } + public boolean hasEnd(){ return false; } + public boolean hasStart(){ return false; } + public final UnknownFieldSet getUnknownFields(){ return null; } + public final boolean isInitialized(){ return false; } + public int getEnd(){ return 0; } + public int getSerializedSize(){ return 0; } + public int getStart(){ return 0; } + public static DescriptorProtos.DescriptorProto.ExtensionRange getDefaultInstance(){ return null; } + public static DescriptorProtos.DescriptorProto.ExtensionRange parseDelimitedFrom(InputStream p0){ return null; } + public static DescriptorProtos.DescriptorProto.ExtensionRange parseDelimitedFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.DescriptorProto.ExtensionRange parseFrom(ByteString p0){ return null; } + public static DescriptorProtos.DescriptorProto.ExtensionRange parseFrom(ByteString p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.DescriptorProto.ExtensionRange parseFrom(CodedInputStream p0){ return null; } + public static DescriptorProtos.DescriptorProto.ExtensionRange parseFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.DescriptorProto.ExtensionRange parseFrom(InputStream p0){ return null; } + public static DescriptorProtos.DescriptorProto.ExtensionRange parseFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.DescriptorProto.ExtensionRange parseFrom(byte[] p0){ return null; } + public static DescriptorProtos.DescriptorProto.ExtensionRange parseFrom(byte[] p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.DescriptorProto.ExtensionRange.Builder newBuilder(){ return null; } + public static DescriptorProtos.DescriptorProto.ExtensionRange.Builder newBuilder(DescriptorProtos.DescriptorProto.ExtensionRange p0){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + public static Parser PARSER = null; + public static int END_FIELD_NUMBER = 0; + public static int START_FIELD_NUMBER = 0; + public void writeTo(CodedOutputStream p0){} + static public class Builder extends GeneratedMessage.Builder implements DescriptorProtos.DescriptorProto.ExtensionRangeOrBuilder + { + protected Builder() {} + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + public DescriptorProtos.DescriptorProto.ExtensionRange build(){ return null; } + public DescriptorProtos.DescriptorProto.ExtensionRange buildPartial(){ return null; } + public DescriptorProtos.DescriptorProto.ExtensionRange getDefaultInstanceForType(){ return null; } + public DescriptorProtos.DescriptorProto.ExtensionRange.Builder clear(){ return null; } + public DescriptorProtos.DescriptorProto.ExtensionRange.Builder clearEnd(){ return null; } + public DescriptorProtos.DescriptorProto.ExtensionRange.Builder clearStart(){ return null; } + public DescriptorProtos.DescriptorProto.ExtensionRange.Builder clone(){ return null; } + public DescriptorProtos.DescriptorProto.ExtensionRange.Builder mergeFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public DescriptorProtos.DescriptorProto.ExtensionRange.Builder mergeFrom(DescriptorProtos.DescriptorProto.ExtensionRange p0){ return null; } + public DescriptorProtos.DescriptorProto.ExtensionRange.Builder mergeFrom(Message p0){ return null; } + public DescriptorProtos.DescriptorProto.ExtensionRange.Builder setEnd(int p0){ return null; } + public DescriptorProtos.DescriptorProto.ExtensionRange.Builder setStart(int p0){ return null; } + public Descriptors.Descriptor getDescriptorForType(){ return null; } + public boolean hasEnd(){ return false; } + public boolean hasStart(){ return false; } + public final boolean isInitialized(){ return false; } + public int getEnd(){ return 0; } + public int getStart(){ return 0; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + } + } + static public interface ExtensionRangeOrBuilder extends MessageOrBuilder + { + boolean hasEnd(); + boolean hasStart(); + int getEnd(); + int getStart(); + } + } + static public class EnumDescriptorProto extends GeneratedMessage implements DescriptorProtos.EnumDescriptorProtoOrBuilder + { + protected EnumDescriptorProto() {} + protected DescriptorProtos.EnumDescriptorProto.Builder newBuilderForType(GeneratedMessage.BuilderParent p0){ return null; } + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + protected Object writeReplace(){ return null; } + public ByteString getNameBytes(){ return null; } + public DescriptorProtos.EnumDescriptorProto getDefaultInstanceForType(){ return null; } + public DescriptorProtos.EnumDescriptorProto.Builder newBuilderForType(){ return null; } + public DescriptorProtos.EnumDescriptorProto.Builder toBuilder(){ return null; } + public DescriptorProtos.EnumOptions getOptions(){ return null; } + public DescriptorProtos.EnumOptionsOrBuilder getOptionsOrBuilder(){ return null; } + public DescriptorProtos.EnumValueDescriptorProto getValue(int p0){ return null; } + public DescriptorProtos.EnumValueDescriptorProtoOrBuilder getValueOrBuilder(int p0){ return null; } + public List getValueOrBuilderList(){ return null; } + public List getValueList(){ return null; } + public Parser getParserForType(){ return null; } + public String getName(){ return null; } + public boolean hasName(){ return false; } + public boolean hasOptions(){ return false; } + public final UnknownFieldSet getUnknownFields(){ return null; } + public final boolean isInitialized(){ return false; } + public int getSerializedSize(){ return 0; } + public int getValueCount(){ return 0; } + public static DescriptorProtos.EnumDescriptorProto getDefaultInstance(){ return null; } + public static DescriptorProtos.EnumDescriptorProto parseDelimitedFrom(InputStream p0){ return null; } + public static DescriptorProtos.EnumDescriptorProto parseDelimitedFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.EnumDescriptorProto parseFrom(ByteString p0){ return null; } + public static DescriptorProtos.EnumDescriptorProto parseFrom(ByteString p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.EnumDescriptorProto parseFrom(CodedInputStream p0){ return null; } + public static DescriptorProtos.EnumDescriptorProto parseFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.EnumDescriptorProto parseFrom(InputStream p0){ return null; } + public static DescriptorProtos.EnumDescriptorProto parseFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.EnumDescriptorProto parseFrom(byte[] p0){ return null; } + public static DescriptorProtos.EnumDescriptorProto parseFrom(byte[] p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.EnumDescriptorProto.Builder newBuilder(){ return null; } + public static DescriptorProtos.EnumDescriptorProto.Builder newBuilder(DescriptorProtos.EnumDescriptorProto p0){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + public static Parser PARSER = null; + public static int NAME_FIELD_NUMBER = 0; + public static int OPTIONS_FIELD_NUMBER = 0; + public static int VALUE_FIELD_NUMBER = 0; + public void writeTo(CodedOutputStream p0){} + static public class Builder extends GeneratedMessage.Builder implements DescriptorProtos.EnumDescriptorProtoOrBuilder + { + protected Builder() {} + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + public ByteString getNameBytes(){ return null; } + public DescriptorProtos.EnumDescriptorProto build(){ return null; } + public DescriptorProtos.EnumDescriptorProto buildPartial(){ return null; } + public DescriptorProtos.EnumDescriptorProto getDefaultInstanceForType(){ return null; } + public DescriptorProtos.EnumDescriptorProto.Builder addAllValue(Iterable p0){ return null; } + public DescriptorProtos.EnumDescriptorProto.Builder addValue(DescriptorProtos.EnumValueDescriptorProto p0){ return null; } + public DescriptorProtos.EnumDescriptorProto.Builder addValue(DescriptorProtos.EnumValueDescriptorProto.Builder p0){ return null; } + public DescriptorProtos.EnumDescriptorProto.Builder addValue(int p0, DescriptorProtos.EnumValueDescriptorProto p1){ return null; } + public DescriptorProtos.EnumDescriptorProto.Builder addValue(int p0, DescriptorProtos.EnumValueDescriptorProto.Builder p1){ return null; } + public DescriptorProtos.EnumDescriptorProto.Builder clear(){ return null; } + public DescriptorProtos.EnumDescriptorProto.Builder clearName(){ return null; } + public DescriptorProtos.EnumDescriptorProto.Builder clearOptions(){ return null; } + public DescriptorProtos.EnumDescriptorProto.Builder clearValue(){ return null; } + public DescriptorProtos.EnumDescriptorProto.Builder clone(){ return null; } + public DescriptorProtos.EnumDescriptorProto.Builder mergeFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public DescriptorProtos.EnumDescriptorProto.Builder mergeFrom(DescriptorProtos.EnumDescriptorProto p0){ return null; } + public DescriptorProtos.EnumDescriptorProto.Builder mergeFrom(Message p0){ return null; } + public DescriptorProtos.EnumDescriptorProto.Builder mergeOptions(DescriptorProtos.EnumOptions p0){ return null; } + public DescriptorProtos.EnumDescriptorProto.Builder removeValue(int p0){ return null; } + public DescriptorProtos.EnumDescriptorProto.Builder setName(String p0){ return null; } + public DescriptorProtos.EnumDescriptorProto.Builder setNameBytes(ByteString p0){ return null; } + public DescriptorProtos.EnumDescriptorProto.Builder setOptions(DescriptorProtos.EnumOptions p0){ return null; } + public DescriptorProtos.EnumDescriptorProto.Builder setOptions(DescriptorProtos.EnumOptions.Builder p0){ return null; } + public DescriptorProtos.EnumDescriptorProto.Builder setValue(int p0, DescriptorProtos.EnumValueDescriptorProto p1){ return null; } + public DescriptorProtos.EnumDescriptorProto.Builder setValue(int p0, DescriptorProtos.EnumValueDescriptorProto.Builder p1){ return null; } + public DescriptorProtos.EnumOptions getOptions(){ return null; } + public DescriptorProtos.EnumOptions.Builder getOptionsBuilder(){ return null; } + public DescriptorProtos.EnumOptionsOrBuilder getOptionsOrBuilder(){ return null; } + public DescriptorProtos.EnumValueDescriptorProto getValue(int p0){ return null; } + public DescriptorProtos.EnumValueDescriptorProto.Builder addValueBuilder(){ return null; } + public DescriptorProtos.EnumValueDescriptorProto.Builder addValueBuilder(int p0){ return null; } + public DescriptorProtos.EnumValueDescriptorProto.Builder getValueBuilder(int p0){ return null; } + public DescriptorProtos.EnumValueDescriptorProtoOrBuilder getValueOrBuilder(int p0){ return null; } + public Descriptors.Descriptor getDescriptorForType(){ return null; } + public List getValueOrBuilderList(){ return null; } + public List getValueList(){ return null; } + public String getName(){ return null; } + public boolean hasName(){ return false; } + public boolean hasOptions(){ return false; } + public final boolean isInitialized(){ return false; } + public int getValueCount(){ return 0; } + public java.util.List getValueBuilderList(){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + } + } + static public class EnumOptions extends GeneratedMessage.ExtendableMessage implements DescriptorProtos.EnumOptionsOrBuilder + { + protected EnumOptions() {} + protected DescriptorProtos.EnumOptions.Builder newBuilderForType(GeneratedMessage.BuilderParent p0){ return null; } + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + protected Object writeReplace(){ return null; } + public DescriptorProtos.EnumOptions getDefaultInstanceForType(){ return null; } + public DescriptorProtos.EnumOptions.Builder newBuilderForType(){ return null; } + public DescriptorProtos.EnumOptions.Builder toBuilder(){ return null; } + public DescriptorProtos.UninterpretedOption getUninterpretedOption(int p0){ return null; } + public DescriptorProtos.UninterpretedOptionOrBuilder getUninterpretedOptionOrBuilder(int p0){ return null; } + public List getUninterpretedOptionOrBuilderList(){ return null; } + public List getUninterpretedOptionList(){ return null; } + public Parser getParserForType(){ return null; } + public boolean getAllowAlias(){ return false; } + public boolean hasAllowAlias(){ return false; } + public final UnknownFieldSet getUnknownFields(){ return null; } + public final boolean isInitialized(){ return false; } + public int getSerializedSize(){ return 0; } + public int getUninterpretedOptionCount(){ return 0; } + public static DescriptorProtos.EnumOptions getDefaultInstance(){ return null; } + public static DescriptorProtos.EnumOptions parseDelimitedFrom(InputStream p0){ return null; } + public static DescriptorProtos.EnumOptions parseDelimitedFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.EnumOptions parseFrom(ByteString p0){ return null; } + public static DescriptorProtos.EnumOptions parseFrom(ByteString p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.EnumOptions parseFrom(CodedInputStream p0){ return null; } + public static DescriptorProtos.EnumOptions parseFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.EnumOptions parseFrom(InputStream p0){ return null; } + public static DescriptorProtos.EnumOptions parseFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.EnumOptions parseFrom(byte[] p0){ return null; } + public static DescriptorProtos.EnumOptions parseFrom(byte[] p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.EnumOptions.Builder newBuilder(){ return null; } + public static DescriptorProtos.EnumOptions.Builder newBuilder(DescriptorProtos.EnumOptions p0){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + public static Parser PARSER = null; + public static int ALLOW_ALIAS_FIELD_NUMBER = 0; + public static int UNINTERPRETED_OPTION_FIELD_NUMBER = 0; + public void writeTo(CodedOutputStream p0){} + static public class Builder extends GeneratedMessage.ExtendableBuilder implements DescriptorProtos.EnumOptionsOrBuilder + { + protected Builder() {} + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + public DescriptorProtos.EnumOptions build(){ return null; } + public DescriptorProtos.EnumOptions buildPartial(){ return null; } + public DescriptorProtos.EnumOptions getDefaultInstanceForType(){ return null; } + public DescriptorProtos.EnumOptions.Builder addAllUninterpretedOption(Iterable p0){ return null; } + public DescriptorProtos.EnumOptions.Builder addUninterpretedOption(DescriptorProtos.UninterpretedOption p0){ return null; } + public DescriptorProtos.EnumOptions.Builder addUninterpretedOption(DescriptorProtos.UninterpretedOption.Builder p0){ return null; } + public DescriptorProtos.EnumOptions.Builder addUninterpretedOption(int p0, DescriptorProtos.UninterpretedOption p1){ return null; } + public DescriptorProtos.EnumOptions.Builder addUninterpretedOption(int p0, DescriptorProtos.UninterpretedOption.Builder p1){ return null; } + public DescriptorProtos.EnumOptions.Builder clear(){ return null; } + public DescriptorProtos.EnumOptions.Builder clearAllowAlias(){ return null; } + public DescriptorProtos.EnumOptions.Builder clearUninterpretedOption(){ return null; } + public DescriptorProtos.EnumOptions.Builder clone(){ return null; } + public DescriptorProtos.EnumOptions.Builder mergeFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public DescriptorProtos.EnumOptions.Builder mergeFrom(DescriptorProtos.EnumOptions p0){ return null; } + public DescriptorProtos.EnumOptions.Builder mergeFrom(Message p0){ return null; } + public DescriptorProtos.EnumOptions.Builder removeUninterpretedOption(int p0){ return null; } + public DescriptorProtos.EnumOptions.Builder setAllowAlias(boolean p0){ return null; } + public DescriptorProtos.EnumOptions.Builder setUninterpretedOption(int p0, DescriptorProtos.UninterpretedOption p1){ return null; } + public DescriptorProtos.EnumOptions.Builder setUninterpretedOption(int p0, DescriptorProtos.UninterpretedOption.Builder p1){ return null; } + public DescriptorProtos.UninterpretedOption getUninterpretedOption(int p0){ return null; } + public DescriptorProtos.UninterpretedOption.Builder addUninterpretedOptionBuilder(){ return null; } + public DescriptorProtos.UninterpretedOption.Builder addUninterpretedOptionBuilder(int p0){ return null; } + public DescriptorProtos.UninterpretedOption.Builder getUninterpretedOptionBuilder(int p0){ return null; } + public DescriptorProtos.UninterpretedOptionOrBuilder getUninterpretedOptionOrBuilder(int p0){ return null; } + public Descriptors.Descriptor getDescriptorForType(){ return null; } + public List getUninterpretedOptionOrBuilderList(){ return null; } + public List getUninterpretedOptionList(){ return null; } + public boolean getAllowAlias(){ return false; } + public boolean hasAllowAlias(){ return false; } + public final boolean isInitialized(){ return false; } + public int getUninterpretedOptionCount(){ return 0; } + public java.util.List getUninterpretedOptionBuilderList(){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + } + } + static public class EnumValueDescriptorProto extends GeneratedMessage implements DescriptorProtos.EnumValueDescriptorProtoOrBuilder + { + protected EnumValueDescriptorProto() {} + protected DescriptorProtos.EnumValueDescriptorProto.Builder newBuilderForType(GeneratedMessage.BuilderParent p0){ return null; } + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + protected Object writeReplace(){ return null; } + public ByteString getNameBytes(){ return null; } + public DescriptorProtos.EnumValueDescriptorProto getDefaultInstanceForType(){ return null; } + public DescriptorProtos.EnumValueDescriptorProto.Builder newBuilderForType(){ return null; } + public DescriptorProtos.EnumValueDescriptorProto.Builder toBuilder(){ return null; } + public DescriptorProtos.EnumValueOptions getOptions(){ return null; } + public DescriptorProtos.EnumValueOptionsOrBuilder getOptionsOrBuilder(){ return null; } + public Parser getParserForType(){ return null; } + public String getName(){ return null; } + public boolean hasName(){ return false; } + public boolean hasNumber(){ return false; } + public boolean hasOptions(){ return false; } + public final UnknownFieldSet getUnknownFields(){ return null; } + public final boolean isInitialized(){ return false; } + public int getNumber(){ return 0; } + public int getSerializedSize(){ return 0; } + public static DescriptorProtos.EnumValueDescriptorProto getDefaultInstance(){ return null; } + public static DescriptorProtos.EnumValueDescriptorProto parseDelimitedFrom(InputStream p0){ return null; } + public static DescriptorProtos.EnumValueDescriptorProto parseDelimitedFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.EnumValueDescriptorProto parseFrom(ByteString p0){ return null; } + public static DescriptorProtos.EnumValueDescriptorProto parseFrom(ByteString p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.EnumValueDescriptorProto parseFrom(CodedInputStream p0){ return null; } + public static DescriptorProtos.EnumValueDescriptorProto parseFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.EnumValueDescriptorProto parseFrom(InputStream p0){ return null; } + public static DescriptorProtos.EnumValueDescriptorProto parseFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.EnumValueDescriptorProto parseFrom(byte[] p0){ return null; } + public static DescriptorProtos.EnumValueDescriptorProto parseFrom(byte[] p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.EnumValueDescriptorProto.Builder newBuilder(){ return null; } + public static DescriptorProtos.EnumValueDescriptorProto.Builder newBuilder(DescriptorProtos.EnumValueDescriptorProto p0){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + public static Parser PARSER = null; + public static int NAME_FIELD_NUMBER = 0; + public static int NUMBER_FIELD_NUMBER = 0; + public static int OPTIONS_FIELD_NUMBER = 0; + public void writeTo(CodedOutputStream p0){} + static public class Builder extends GeneratedMessage.Builder implements DescriptorProtos.EnumValueDescriptorProtoOrBuilder + { + protected Builder() {} + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + public ByteString getNameBytes(){ return null; } + public DescriptorProtos.EnumValueDescriptorProto build(){ return null; } + public DescriptorProtos.EnumValueDescriptorProto buildPartial(){ return null; } + public DescriptorProtos.EnumValueDescriptorProto getDefaultInstanceForType(){ return null; } + public DescriptorProtos.EnumValueDescriptorProto.Builder clear(){ return null; } + public DescriptorProtos.EnumValueDescriptorProto.Builder clearName(){ return null; } + public DescriptorProtos.EnumValueDescriptorProto.Builder clearNumber(){ return null; } + public DescriptorProtos.EnumValueDescriptorProto.Builder clearOptions(){ return null; } + public DescriptorProtos.EnumValueDescriptorProto.Builder clone(){ return null; } + public DescriptorProtos.EnumValueDescriptorProto.Builder mergeFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public DescriptorProtos.EnumValueDescriptorProto.Builder mergeFrom(DescriptorProtos.EnumValueDescriptorProto p0){ return null; } + public DescriptorProtos.EnumValueDescriptorProto.Builder mergeFrom(Message p0){ return null; } + public DescriptorProtos.EnumValueDescriptorProto.Builder mergeOptions(DescriptorProtos.EnumValueOptions p0){ return null; } + public DescriptorProtos.EnumValueDescriptorProto.Builder setName(String p0){ return null; } + public DescriptorProtos.EnumValueDescriptorProto.Builder setNameBytes(ByteString p0){ return null; } + public DescriptorProtos.EnumValueDescriptorProto.Builder setNumber(int p0){ return null; } + public DescriptorProtos.EnumValueDescriptorProto.Builder setOptions(DescriptorProtos.EnumValueOptions p0){ return null; } + public DescriptorProtos.EnumValueDescriptorProto.Builder setOptions(DescriptorProtos.EnumValueOptions.Builder p0){ return null; } + public DescriptorProtos.EnumValueOptions getOptions(){ return null; } + public DescriptorProtos.EnumValueOptions.Builder getOptionsBuilder(){ return null; } + public DescriptorProtos.EnumValueOptionsOrBuilder getOptionsOrBuilder(){ return null; } + public Descriptors.Descriptor getDescriptorForType(){ return null; } + public String getName(){ return null; } + public boolean hasName(){ return false; } + public boolean hasNumber(){ return false; } + public boolean hasOptions(){ return false; } + public final boolean isInitialized(){ return false; } + public int getNumber(){ return 0; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + } + } + static public class EnumValueOptions extends GeneratedMessage.ExtendableMessage implements DescriptorProtos.EnumValueOptionsOrBuilder + { + protected EnumValueOptions() {} + protected DescriptorProtos.EnumValueOptions.Builder newBuilderForType(GeneratedMessage.BuilderParent p0){ return null; } + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + protected Object writeReplace(){ return null; } + public DescriptorProtos.EnumValueOptions getDefaultInstanceForType(){ return null; } + public DescriptorProtos.EnumValueOptions.Builder newBuilderForType(){ return null; } + public DescriptorProtos.EnumValueOptions.Builder toBuilder(){ return null; } + public DescriptorProtos.UninterpretedOption getUninterpretedOption(int p0){ return null; } + public DescriptorProtos.UninterpretedOptionOrBuilder getUninterpretedOptionOrBuilder(int p0){ return null; } + public List getUninterpretedOptionOrBuilderList(){ return null; } + public List getUninterpretedOptionList(){ return null; } + public Parser getParserForType(){ return null; } + public final UnknownFieldSet getUnknownFields(){ return null; } + public final boolean isInitialized(){ return false; } + public int getSerializedSize(){ return 0; } + public int getUninterpretedOptionCount(){ return 0; } + public static DescriptorProtos.EnumValueOptions getDefaultInstance(){ return null; } + public static DescriptorProtos.EnumValueOptions parseDelimitedFrom(InputStream p0){ return null; } + public static DescriptorProtos.EnumValueOptions parseDelimitedFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.EnumValueOptions parseFrom(ByteString p0){ return null; } + public static DescriptorProtos.EnumValueOptions parseFrom(ByteString p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.EnumValueOptions parseFrom(CodedInputStream p0){ return null; } + public static DescriptorProtos.EnumValueOptions parseFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.EnumValueOptions parseFrom(InputStream p0){ return null; } + public static DescriptorProtos.EnumValueOptions parseFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.EnumValueOptions parseFrom(byte[] p0){ return null; } + public static DescriptorProtos.EnumValueOptions parseFrom(byte[] p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.EnumValueOptions.Builder newBuilder(){ return null; } + public static DescriptorProtos.EnumValueOptions.Builder newBuilder(DescriptorProtos.EnumValueOptions p0){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + public static Parser PARSER = null; + public static int UNINTERPRETED_OPTION_FIELD_NUMBER = 0; + public void writeTo(CodedOutputStream p0){} + static public class Builder extends GeneratedMessage.ExtendableBuilder implements DescriptorProtos.EnumValueOptionsOrBuilder + { + protected Builder() {} + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + public DescriptorProtos.EnumValueOptions build(){ return null; } + public DescriptorProtos.EnumValueOptions buildPartial(){ return null; } + public DescriptorProtos.EnumValueOptions getDefaultInstanceForType(){ return null; } + public DescriptorProtos.EnumValueOptions.Builder addAllUninterpretedOption(Iterable p0){ return null; } + public DescriptorProtos.EnumValueOptions.Builder addUninterpretedOption(DescriptorProtos.UninterpretedOption p0){ return null; } + public DescriptorProtos.EnumValueOptions.Builder addUninterpretedOption(DescriptorProtos.UninterpretedOption.Builder p0){ return null; } + public DescriptorProtos.EnumValueOptions.Builder addUninterpretedOption(int p0, DescriptorProtos.UninterpretedOption p1){ return null; } + public DescriptorProtos.EnumValueOptions.Builder addUninterpretedOption(int p0, DescriptorProtos.UninterpretedOption.Builder p1){ return null; } + public DescriptorProtos.EnumValueOptions.Builder clear(){ return null; } + public DescriptorProtos.EnumValueOptions.Builder clearUninterpretedOption(){ return null; } + public DescriptorProtos.EnumValueOptions.Builder clone(){ return null; } + public DescriptorProtos.EnumValueOptions.Builder mergeFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public DescriptorProtos.EnumValueOptions.Builder mergeFrom(DescriptorProtos.EnumValueOptions p0){ return null; } + public DescriptorProtos.EnumValueOptions.Builder mergeFrom(Message p0){ return null; } + public DescriptorProtos.EnumValueOptions.Builder removeUninterpretedOption(int p0){ return null; } + public DescriptorProtos.EnumValueOptions.Builder setUninterpretedOption(int p0, DescriptorProtos.UninterpretedOption p1){ return null; } + public DescriptorProtos.EnumValueOptions.Builder setUninterpretedOption(int p0, DescriptorProtos.UninterpretedOption.Builder p1){ return null; } + public DescriptorProtos.UninterpretedOption getUninterpretedOption(int p0){ return null; } + public DescriptorProtos.UninterpretedOption.Builder addUninterpretedOptionBuilder(){ return null; } + public DescriptorProtos.UninterpretedOption.Builder addUninterpretedOptionBuilder(int p0){ return null; } + public DescriptorProtos.UninterpretedOption.Builder getUninterpretedOptionBuilder(int p0){ return null; } + public DescriptorProtos.UninterpretedOptionOrBuilder getUninterpretedOptionOrBuilder(int p0){ return null; } + public Descriptors.Descriptor getDescriptorForType(){ return null; } + public List getUninterpretedOptionOrBuilderList(){ return null; } + public List getUninterpretedOptionList(){ return null; } + public final boolean isInitialized(){ return false; } + public int getUninterpretedOptionCount(){ return 0; } + public java.util.List getUninterpretedOptionBuilderList(){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + } + } + static public class FieldDescriptorProto extends GeneratedMessage implements DescriptorProtos.FieldDescriptorProtoOrBuilder + { + protected FieldDescriptorProto() {} + protected DescriptorProtos.FieldDescriptorProto.Builder newBuilderForType(GeneratedMessage.BuilderParent p0){ return null; } + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + protected Object writeReplace(){ return null; } + public ByteString getDefaultValueBytes(){ return null; } + public ByteString getExtendeeBytes(){ return null; } + public ByteString getNameBytes(){ return null; } + public ByteString getTypeNameBytes(){ return null; } + public DescriptorProtos.FieldDescriptorProto getDefaultInstanceForType(){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder newBuilderForType(){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder toBuilder(){ return null; } + public DescriptorProtos.FieldDescriptorProto.Label getLabel(){ return null; } + public DescriptorProtos.FieldDescriptorProto.Type getType(){ return null; } + public DescriptorProtos.FieldOptions getOptions(){ return null; } + public DescriptorProtos.FieldOptionsOrBuilder getOptionsOrBuilder(){ return null; } + public Parser getParserForType(){ return null; } + public String getDefaultValue(){ return null; } + public String getExtendee(){ return null; } + public String getName(){ return null; } + public String getTypeName(){ return null; } + public boolean hasDefaultValue(){ return false; } + public boolean hasExtendee(){ return false; } + public boolean hasLabel(){ return false; } + public boolean hasName(){ return false; } + public boolean hasNumber(){ return false; } + public boolean hasOptions(){ return false; } + public boolean hasType(){ return false; } + public boolean hasTypeName(){ return false; } + public final UnknownFieldSet getUnknownFields(){ return null; } + public final boolean isInitialized(){ return false; } + public int getNumber(){ return 0; } + public int getSerializedSize(){ return 0; } + public static DescriptorProtos.FieldDescriptorProto getDefaultInstance(){ return null; } + public static DescriptorProtos.FieldDescriptorProto parseDelimitedFrom(InputStream p0){ return null; } + public static DescriptorProtos.FieldDescriptorProto parseDelimitedFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.FieldDescriptorProto parseFrom(ByteString p0){ return null; } + public static DescriptorProtos.FieldDescriptorProto parseFrom(ByteString p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.FieldDescriptorProto parseFrom(CodedInputStream p0){ return null; } + public static DescriptorProtos.FieldDescriptorProto parseFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.FieldDescriptorProto parseFrom(InputStream p0){ return null; } + public static DescriptorProtos.FieldDescriptorProto parseFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.FieldDescriptorProto parseFrom(byte[] p0){ return null; } + public static DescriptorProtos.FieldDescriptorProto parseFrom(byte[] p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.FieldDescriptorProto.Builder newBuilder(){ return null; } + public static DescriptorProtos.FieldDescriptorProto.Builder newBuilder(DescriptorProtos.FieldDescriptorProto p0){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + public static Parser PARSER = null; + public static int DEFAULT_VALUE_FIELD_NUMBER = 0; + public static int EXTENDEE_FIELD_NUMBER = 0; + public static int LABEL_FIELD_NUMBER = 0; + public static int NAME_FIELD_NUMBER = 0; + public static int NUMBER_FIELD_NUMBER = 0; + public static int OPTIONS_FIELD_NUMBER = 0; + public static int TYPE_FIELD_NUMBER = 0; + public static int TYPE_NAME_FIELD_NUMBER = 0; + public void writeTo(CodedOutputStream p0){} + static public class Builder extends GeneratedMessage.Builder implements DescriptorProtos.FieldDescriptorProtoOrBuilder + { + protected Builder() {} + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + public ByteString getDefaultValueBytes(){ return null; } + public ByteString getExtendeeBytes(){ return null; } + public ByteString getNameBytes(){ return null; } + public ByteString getTypeNameBytes(){ return null; } + public DescriptorProtos.FieldDescriptorProto build(){ return null; } + public DescriptorProtos.FieldDescriptorProto buildPartial(){ return null; } + public DescriptorProtos.FieldDescriptorProto getDefaultInstanceForType(){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder clear(){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder clearDefaultValue(){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder clearExtendee(){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder clearLabel(){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder clearName(){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder clearNumber(){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder clearOptions(){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder clearType(){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder clearTypeName(){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder clone(){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder mergeFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder mergeFrom(DescriptorProtos.FieldDescriptorProto p0){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder mergeFrom(Message p0){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder mergeOptions(DescriptorProtos.FieldOptions p0){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder setDefaultValue(String p0){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder setDefaultValueBytes(ByteString p0){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder setExtendee(String p0){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder setExtendeeBytes(ByteString p0){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder setLabel(DescriptorProtos.FieldDescriptorProto.Label p0){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder setName(String p0){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder setNameBytes(ByteString p0){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder setNumber(int p0){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder setOptions(DescriptorProtos.FieldOptions p0){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder setOptions(DescriptorProtos.FieldOptions.Builder p0){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder setType(DescriptorProtos.FieldDescriptorProto.Type p0){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder setTypeName(String p0){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder setTypeNameBytes(ByteString p0){ return null; } + public DescriptorProtos.FieldDescriptorProto.Label getLabel(){ return null; } + public DescriptorProtos.FieldDescriptorProto.Type getType(){ return null; } + public DescriptorProtos.FieldOptions getOptions(){ return null; } + public DescriptorProtos.FieldOptions.Builder getOptionsBuilder(){ return null; } + public DescriptorProtos.FieldOptionsOrBuilder getOptionsOrBuilder(){ return null; } + public Descriptors.Descriptor getDescriptorForType(){ return null; } + public String getDefaultValue(){ return null; } + public String getExtendee(){ return null; } + public String getName(){ return null; } + public String getTypeName(){ return null; } + public boolean hasDefaultValue(){ return false; } + public boolean hasExtendee(){ return false; } + public boolean hasLabel(){ return false; } + public boolean hasName(){ return false; } + public boolean hasNumber(){ return false; } + public boolean hasOptions(){ return false; } + public boolean hasType(){ return false; } + public boolean hasTypeName(){ return false; } + public final boolean isInitialized(){ return false; } + public int getNumber(){ return 0; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + } + static public enum Label implements Internal.EnumLite + { + LABEL_OPTIONAL, LABEL_REPEATED, LABEL_REQUIRED; + private Label() {} + public final Descriptors.EnumDescriptor getDescriptorForType(){ return null; } + public final Descriptors.EnumValueDescriptor getValueDescriptor(){ return null; } + public final int getNumber(){ return 0; } + public static Descriptors.EnumDescriptor getDescriptor(){ return null; } + public static Internal.EnumLiteMap internalGetValueMap(){ return null; } + public static int LABEL_OPTIONAL_VALUE = 0; + public static int LABEL_REPEATED_VALUE = 0; + public static int LABEL_REQUIRED_VALUE = 0; + } + static public enum Type implements Internal.EnumLite + { + TYPE_BOOL, TYPE_BYTES, TYPE_DOUBLE, TYPE_ENUM, TYPE_FIXED32, TYPE_FIXED64, TYPE_FLOAT, TYPE_GROUP, TYPE_INT32, TYPE_INT64, TYPE_MESSAGE, TYPE_SFIXED32, TYPE_SFIXED64, TYPE_SINT32, TYPE_SINT64, TYPE_STRING, TYPE_UINT32, TYPE_UINT64; + private Type() {} + public final Descriptors.EnumDescriptor getDescriptorForType(){ return null; } + public final Descriptors.EnumValueDescriptor getValueDescriptor(){ return null; } + public final int getNumber(){ return 0; } + public static Descriptors.EnumDescriptor getDescriptor(){ return null; } + public static Internal.EnumLiteMap internalGetValueMap(){ return null; } + public static int TYPE_BOOL_VALUE = 0; + public static int TYPE_BYTES_VALUE = 0; + public static int TYPE_DOUBLE_VALUE = 0; + public static int TYPE_ENUM_VALUE = 0; + public static int TYPE_FIXED32_VALUE = 0; + public static int TYPE_FIXED64_VALUE = 0; + public static int TYPE_FLOAT_VALUE = 0; + public static int TYPE_GROUP_VALUE = 0; + public static int TYPE_INT32_VALUE = 0; + public static int TYPE_INT64_VALUE = 0; + public static int TYPE_MESSAGE_VALUE = 0; + public static int TYPE_SFIXED32_VALUE = 0; + public static int TYPE_SFIXED64_VALUE = 0; + public static int TYPE_SINT32_VALUE = 0; + public static int TYPE_SINT64_VALUE = 0; + public static int TYPE_STRING_VALUE = 0; + public static int TYPE_UINT32_VALUE = 0; + public static int TYPE_UINT64_VALUE = 0; + } + } + static public class FieldOptions extends GeneratedMessage.ExtendableMessage implements DescriptorProtos.FieldOptionsOrBuilder + { + protected FieldOptions() {} + protected DescriptorProtos.FieldOptions.Builder newBuilderForType(GeneratedMessage.BuilderParent p0){ return null; } + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + protected Object writeReplace(){ return null; } + public ByteString getExperimentalMapKeyBytes(){ return null; } + public DescriptorProtos.FieldOptions getDefaultInstanceForType(){ return null; } + public DescriptorProtos.FieldOptions.Builder newBuilderForType(){ return null; } + public DescriptorProtos.FieldOptions.Builder toBuilder(){ return null; } + public DescriptorProtos.FieldOptions.CType getCtype(){ return null; } + public DescriptorProtos.UninterpretedOption getUninterpretedOption(int p0){ return null; } + public DescriptorProtos.UninterpretedOptionOrBuilder getUninterpretedOptionOrBuilder(int p0){ return null; } + public List getUninterpretedOptionOrBuilderList(){ return null; } + public List getUninterpretedOptionList(){ return null; } + public Parser getParserForType(){ return null; } + public String getExperimentalMapKey(){ return null; } + public boolean getDeprecated(){ return false; } + public boolean getLazy(){ return false; } + public boolean getPacked(){ return false; } + public boolean getWeak(){ return false; } + public boolean hasCtype(){ return false; } + public boolean hasDeprecated(){ return false; } + public boolean hasExperimentalMapKey(){ return false; } + public boolean hasLazy(){ return false; } + public boolean hasPacked(){ return false; } + public boolean hasWeak(){ return false; } + public final UnknownFieldSet getUnknownFields(){ return null; } + public final boolean isInitialized(){ return false; } + public int getSerializedSize(){ return 0; } + public int getUninterpretedOptionCount(){ return 0; } + public static DescriptorProtos.FieldOptions getDefaultInstance(){ return null; } + public static DescriptorProtos.FieldOptions parseDelimitedFrom(InputStream p0){ return null; } + public static DescriptorProtos.FieldOptions parseDelimitedFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.FieldOptions parseFrom(ByteString p0){ return null; } + public static DescriptorProtos.FieldOptions parseFrom(ByteString p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.FieldOptions parseFrom(CodedInputStream p0){ return null; } + public static DescriptorProtos.FieldOptions parseFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.FieldOptions parseFrom(InputStream p0){ return null; } + public static DescriptorProtos.FieldOptions parseFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.FieldOptions parseFrom(byte[] p0){ return null; } + public static DescriptorProtos.FieldOptions parseFrom(byte[] p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.FieldOptions.Builder newBuilder(){ return null; } + public static DescriptorProtos.FieldOptions.Builder newBuilder(DescriptorProtos.FieldOptions p0){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + public static Parser PARSER = null; + public static int CTYPE_FIELD_NUMBER = 0; + public static int DEPRECATED_FIELD_NUMBER = 0; + public static int EXPERIMENTAL_MAP_KEY_FIELD_NUMBER = 0; + public static int LAZY_FIELD_NUMBER = 0; + public static int PACKED_FIELD_NUMBER = 0; + public static int UNINTERPRETED_OPTION_FIELD_NUMBER = 0; + public static int WEAK_FIELD_NUMBER = 0; + public void writeTo(CodedOutputStream p0){} + static public class Builder extends GeneratedMessage.ExtendableBuilder implements DescriptorProtos.FieldOptionsOrBuilder + { + protected Builder() {} + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + public ByteString getExperimentalMapKeyBytes(){ return null; } + public DescriptorProtos.FieldOptions build(){ return null; } + public DescriptorProtos.FieldOptions buildPartial(){ return null; } + public DescriptorProtos.FieldOptions getDefaultInstanceForType(){ return null; } + public DescriptorProtos.FieldOptions.Builder addAllUninterpretedOption(Iterable p0){ return null; } + public DescriptorProtos.FieldOptions.Builder addUninterpretedOption(DescriptorProtos.UninterpretedOption p0){ return null; } + public DescriptorProtos.FieldOptions.Builder addUninterpretedOption(DescriptorProtos.UninterpretedOption.Builder p0){ return null; } + public DescriptorProtos.FieldOptions.Builder addUninterpretedOption(int p0, DescriptorProtos.UninterpretedOption p1){ return null; } + public DescriptorProtos.FieldOptions.Builder addUninterpretedOption(int p0, DescriptorProtos.UninterpretedOption.Builder p1){ return null; } + public DescriptorProtos.FieldOptions.Builder clear(){ return null; } + public DescriptorProtos.FieldOptions.Builder clearCtype(){ return null; } + public DescriptorProtos.FieldOptions.Builder clearDeprecated(){ return null; } + public DescriptorProtos.FieldOptions.Builder clearExperimentalMapKey(){ return null; } + public DescriptorProtos.FieldOptions.Builder clearLazy(){ return null; } + public DescriptorProtos.FieldOptions.Builder clearPacked(){ return null; } + public DescriptorProtos.FieldOptions.Builder clearUninterpretedOption(){ return null; } + public DescriptorProtos.FieldOptions.Builder clearWeak(){ return null; } + public DescriptorProtos.FieldOptions.Builder clone(){ return null; } + public DescriptorProtos.FieldOptions.Builder mergeFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public DescriptorProtos.FieldOptions.Builder mergeFrom(DescriptorProtos.FieldOptions p0){ return null; } + public DescriptorProtos.FieldOptions.Builder mergeFrom(Message p0){ return null; } + public DescriptorProtos.FieldOptions.Builder removeUninterpretedOption(int p0){ return null; } + public DescriptorProtos.FieldOptions.Builder setCtype(DescriptorProtos.FieldOptions.CType p0){ return null; } + public DescriptorProtos.FieldOptions.Builder setDeprecated(boolean p0){ return null; } + public DescriptorProtos.FieldOptions.Builder setExperimentalMapKey(String p0){ return null; } + public DescriptorProtos.FieldOptions.Builder setExperimentalMapKeyBytes(ByteString p0){ return null; } + public DescriptorProtos.FieldOptions.Builder setLazy(boolean p0){ return null; } + public DescriptorProtos.FieldOptions.Builder setPacked(boolean p0){ return null; } + public DescriptorProtos.FieldOptions.Builder setUninterpretedOption(int p0, DescriptorProtos.UninterpretedOption p1){ return null; } + public DescriptorProtos.FieldOptions.Builder setUninterpretedOption(int p0, DescriptorProtos.UninterpretedOption.Builder p1){ return null; } + public DescriptorProtos.FieldOptions.Builder setWeak(boolean p0){ return null; } + public DescriptorProtos.FieldOptions.CType getCtype(){ return null; } + public DescriptorProtos.UninterpretedOption getUninterpretedOption(int p0){ return null; } + public DescriptorProtos.UninterpretedOption.Builder addUninterpretedOptionBuilder(){ return null; } + public DescriptorProtos.UninterpretedOption.Builder addUninterpretedOptionBuilder(int p0){ return null; } + public DescriptorProtos.UninterpretedOption.Builder getUninterpretedOptionBuilder(int p0){ return null; } + public DescriptorProtos.UninterpretedOptionOrBuilder getUninterpretedOptionOrBuilder(int p0){ return null; } + public Descriptors.Descriptor getDescriptorForType(){ return null; } + public List getUninterpretedOptionOrBuilderList(){ return null; } + public List getUninterpretedOptionList(){ return null; } + public String getExperimentalMapKey(){ return null; } + public boolean getDeprecated(){ return false; } + public boolean getLazy(){ return false; } + public boolean getPacked(){ return false; } + public boolean getWeak(){ return false; } + public boolean hasCtype(){ return false; } + public boolean hasDeprecated(){ return false; } + public boolean hasExperimentalMapKey(){ return false; } + public boolean hasLazy(){ return false; } + public boolean hasPacked(){ return false; } + public boolean hasWeak(){ return false; } + public final boolean isInitialized(){ return false; } + public int getUninterpretedOptionCount(){ return 0; } + public java.util.List getUninterpretedOptionBuilderList(){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + } + static public enum CType implements Internal.EnumLite + { + CORD, STRING, STRING_PIECE; + private CType() {} + public final Descriptors.EnumDescriptor getDescriptorForType(){ return null; } + public final Descriptors.EnumValueDescriptor getValueDescriptor(){ return null; } + public final int getNumber(){ return 0; } + public static Descriptors.EnumDescriptor getDescriptor(){ return null; } + public static Internal.EnumLiteMap internalGetValueMap(){ return null; } + public static int CORD_VALUE = 0; + public static int STRING_PIECE_VALUE = 0; + public static int STRING_VALUE = 0; + } + } + static public class FileDescriptorProto extends GeneratedMessage implements DescriptorProtos.FileDescriptorProtoOrBuilder + { + protected FileDescriptorProto() {} + protected DescriptorProtos.FileDescriptorProto.Builder newBuilderForType(GeneratedMessage.BuilderParent p0){ return null; } + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + protected Object writeReplace(){ return null; } + public ByteString getDependencyBytes(int p0){ return null; } + public ByteString getNameBytes(){ return null; } + public ByteString getPackageBytes(){ return null; } + public DescriptorProtos.DescriptorProto getMessageType(int p0){ return null; } + public DescriptorProtos.DescriptorProtoOrBuilder getMessageTypeOrBuilder(int p0){ return null; } + public DescriptorProtos.EnumDescriptorProto getEnumType(int p0){ return null; } + public DescriptorProtos.EnumDescriptorProtoOrBuilder getEnumTypeOrBuilder(int p0){ return null; } + public DescriptorProtos.FieldDescriptorProto getExtension(int p0){ return null; } + public DescriptorProtos.FieldDescriptorProtoOrBuilder getExtensionOrBuilder(int p0){ return null; } + public DescriptorProtos.FileDescriptorProto getDefaultInstanceForType(){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder newBuilderForType(){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder toBuilder(){ return null; } + public DescriptorProtos.FileOptions getOptions(){ return null; } + public DescriptorProtos.FileOptionsOrBuilder getOptionsOrBuilder(){ return null; } + public DescriptorProtos.ServiceDescriptorProto getService(int p0){ return null; } + public DescriptorProtos.ServiceDescriptorProtoOrBuilder getServiceOrBuilder(int p0){ return null; } + public DescriptorProtos.SourceCodeInfo getSourceCodeInfo(){ return null; } + public DescriptorProtos.SourceCodeInfoOrBuilder getSourceCodeInfoOrBuilder(){ return null; } + public List getMessageTypeOrBuilderList(){ return null; } + public List getEnumTypeOrBuilderList(){ return null; } + public List getExtensionOrBuilderList(){ return null; } + public List getServiceOrBuilderList(){ return null; } + public List getMessageTypeList(){ return null; } + public List getEnumTypeList(){ return null; } + public List getExtensionList(){ return null; } + public List getServiceList(){ return null; } + public List getPublicDependencyList(){ return null; } + public List getWeakDependencyList(){ return null; } + public List getDependencyList(){ return null; } + public Parser getParserForType(){ return null; } + public String getDependency(int p0){ return null; } + public String getName(){ return null; } + public String getPackage(){ return null; } + public boolean hasName(){ return false; } + public boolean hasOptions(){ return false; } + public boolean hasPackage(){ return false; } + public boolean hasSourceCodeInfo(){ return false; } + public final UnknownFieldSet getUnknownFields(){ return null; } + public final boolean isInitialized(){ return false; } + public int getDependencyCount(){ return 0; } + public int getEnumTypeCount(){ return 0; } + public int getExtensionCount(){ return 0; } + public int getMessageTypeCount(){ return 0; } + public int getPublicDependency(int p0){ return 0; } + public int getPublicDependencyCount(){ return 0; } + public int getSerializedSize(){ return 0; } + public int getServiceCount(){ return 0; } + public int getWeakDependency(int p0){ return 0; } + public int getWeakDependencyCount(){ return 0; } + public static DescriptorProtos.FileDescriptorProto getDefaultInstance(){ return null; } + public static DescriptorProtos.FileDescriptorProto parseDelimitedFrom(InputStream p0){ return null; } + public static DescriptorProtos.FileDescriptorProto parseDelimitedFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.FileDescriptorProto parseFrom(ByteString p0){ return null; } + public static DescriptorProtos.FileDescriptorProto parseFrom(ByteString p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.FileDescriptorProto parseFrom(CodedInputStream p0){ return null; } + public static DescriptorProtos.FileDescriptorProto parseFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.FileDescriptorProto parseFrom(InputStream p0){ return null; } + public static DescriptorProtos.FileDescriptorProto parseFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.FileDescriptorProto parseFrom(byte[] p0){ return null; } + public static DescriptorProtos.FileDescriptorProto parseFrom(byte[] p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.FileDescriptorProto.Builder newBuilder(){ return null; } + public static DescriptorProtos.FileDescriptorProto.Builder newBuilder(DescriptorProtos.FileDescriptorProto p0){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + public static Parser PARSER = null; + public static int DEPENDENCY_FIELD_NUMBER = 0; + public static int ENUM_TYPE_FIELD_NUMBER = 0; + public static int EXTENSION_FIELD_NUMBER = 0; + public static int MESSAGE_TYPE_FIELD_NUMBER = 0; + public static int NAME_FIELD_NUMBER = 0; + public static int OPTIONS_FIELD_NUMBER = 0; + public static int PACKAGE_FIELD_NUMBER = 0; + public static int PUBLIC_DEPENDENCY_FIELD_NUMBER = 0; + public static int SERVICE_FIELD_NUMBER = 0; + public static int SOURCE_CODE_INFO_FIELD_NUMBER = 0; + public static int WEAK_DEPENDENCY_FIELD_NUMBER = 0; + public void writeTo(CodedOutputStream p0){} + static public class Builder extends GeneratedMessage.Builder implements DescriptorProtos.FileDescriptorProtoOrBuilder + { + protected Builder() {} + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + public ByteString getDependencyBytes(int p0){ return null; } + public ByteString getNameBytes(){ return null; } + public ByteString getPackageBytes(){ return null; } + public DescriptorProtos.DescriptorProto getMessageType(int p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder addMessageTypeBuilder(){ return null; } + public DescriptorProtos.DescriptorProto.Builder addMessageTypeBuilder(int p0){ return null; } + public DescriptorProtos.DescriptorProto.Builder getMessageTypeBuilder(int p0){ return null; } + public DescriptorProtos.DescriptorProtoOrBuilder getMessageTypeOrBuilder(int p0){ return null; } + public DescriptorProtos.EnumDescriptorProto getEnumType(int p0){ return null; } + public DescriptorProtos.EnumDescriptorProto.Builder addEnumTypeBuilder(){ return null; } + public DescriptorProtos.EnumDescriptorProto.Builder addEnumTypeBuilder(int p0){ return null; } + public DescriptorProtos.EnumDescriptorProto.Builder getEnumTypeBuilder(int p0){ return null; } + public DescriptorProtos.EnumDescriptorProtoOrBuilder getEnumTypeOrBuilder(int p0){ return null; } + public DescriptorProtos.FieldDescriptorProto getExtension(int p0){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder addExtensionBuilder(){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder addExtensionBuilder(int p0){ return null; } + public DescriptorProtos.FieldDescriptorProto.Builder getExtensionBuilder(int p0){ return null; } + public DescriptorProtos.FieldDescriptorProtoOrBuilder getExtensionOrBuilder(int p0){ return null; } + public DescriptorProtos.FileDescriptorProto build(){ return null; } + public DescriptorProtos.FileDescriptorProto buildPartial(){ return null; } + public DescriptorProtos.FileDescriptorProto getDefaultInstanceForType(){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder addAllDependency(Iterable p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder addAllEnumType(Iterable p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder addAllExtension(Iterable p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder addAllMessageType(Iterable p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder addAllPublicDependency(Iterable p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder addAllService(Iterable p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder addAllWeakDependency(Iterable p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder addDependency(String p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder addDependencyBytes(ByteString p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder addEnumType(DescriptorProtos.EnumDescriptorProto p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder addEnumType(DescriptorProtos.EnumDescriptorProto.Builder p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder addEnumType(int p0, DescriptorProtos.EnumDescriptorProto p1){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder addEnumType(int p0, DescriptorProtos.EnumDescriptorProto.Builder p1){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder addExtension(DescriptorProtos.FieldDescriptorProto p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder addExtension(DescriptorProtos.FieldDescriptorProto.Builder p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder addExtension(int p0, DescriptorProtos.FieldDescriptorProto p1){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder addExtension(int p0, DescriptorProtos.FieldDescriptorProto.Builder p1){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder addMessageType(DescriptorProtos.DescriptorProto p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder addMessageType(DescriptorProtos.DescriptorProto.Builder p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder addMessageType(int p0, DescriptorProtos.DescriptorProto p1){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder addMessageType(int p0, DescriptorProtos.DescriptorProto.Builder p1){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder addPublicDependency(int p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder addService(DescriptorProtos.ServiceDescriptorProto p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder addService(DescriptorProtos.ServiceDescriptorProto.Builder p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder addService(int p0, DescriptorProtos.ServiceDescriptorProto p1){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder addService(int p0, DescriptorProtos.ServiceDescriptorProto.Builder p1){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder addWeakDependency(int p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder clear(){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder clearDependency(){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder clearEnumType(){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder clearExtension(){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder clearMessageType(){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder clearName(){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder clearOptions(){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder clearPackage(){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder clearPublicDependency(){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder clearService(){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder clearSourceCodeInfo(){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder clearWeakDependency(){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder clone(){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder mergeFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder mergeFrom(DescriptorProtos.FileDescriptorProto p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder mergeFrom(Message p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder mergeOptions(DescriptorProtos.FileOptions p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder mergeSourceCodeInfo(DescriptorProtos.SourceCodeInfo p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder removeEnumType(int p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder removeExtension(int p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder removeMessageType(int p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder removeService(int p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder setDependency(int p0, String p1){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder setEnumType(int p0, DescriptorProtos.EnumDescriptorProto p1){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder setEnumType(int p0, DescriptorProtos.EnumDescriptorProto.Builder p1){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder setExtension(int p0, DescriptorProtos.FieldDescriptorProto p1){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder setExtension(int p0, DescriptorProtos.FieldDescriptorProto.Builder p1){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder setMessageType(int p0, DescriptorProtos.DescriptorProto p1){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder setMessageType(int p0, DescriptorProtos.DescriptorProto.Builder p1){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder setName(String p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder setNameBytes(ByteString p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder setOptions(DescriptorProtos.FileOptions p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder setOptions(DescriptorProtos.FileOptions.Builder p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder setPackage(String p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder setPackageBytes(ByteString p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder setPublicDependency(int p0, int p1){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder setService(int p0, DescriptorProtos.ServiceDescriptorProto p1){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder setService(int p0, DescriptorProtos.ServiceDescriptorProto.Builder p1){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder setSourceCodeInfo(DescriptorProtos.SourceCodeInfo p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder setSourceCodeInfo(DescriptorProtos.SourceCodeInfo.Builder p0){ return null; } + public DescriptorProtos.FileDescriptorProto.Builder setWeakDependency(int p0, int p1){ return null; } + public DescriptorProtos.FileOptions getOptions(){ return null; } + public DescriptorProtos.FileOptions.Builder getOptionsBuilder(){ return null; } + public DescriptorProtos.FileOptionsOrBuilder getOptionsOrBuilder(){ return null; } + public DescriptorProtos.ServiceDescriptorProto getService(int p0){ return null; } + public DescriptorProtos.ServiceDescriptorProto.Builder addServiceBuilder(){ return null; } + public DescriptorProtos.ServiceDescriptorProto.Builder addServiceBuilder(int p0){ return null; } + public DescriptorProtos.ServiceDescriptorProto.Builder getServiceBuilder(int p0){ return null; } + public DescriptorProtos.ServiceDescriptorProtoOrBuilder getServiceOrBuilder(int p0){ return null; } + public DescriptorProtos.SourceCodeInfo getSourceCodeInfo(){ return null; } + public DescriptorProtos.SourceCodeInfo.Builder getSourceCodeInfoBuilder(){ return null; } + public DescriptorProtos.SourceCodeInfoOrBuilder getSourceCodeInfoOrBuilder(){ return null; } + public Descriptors.Descriptor getDescriptorForType(){ return null; } + public List getMessageTypeOrBuilderList(){ return null; } + public List getEnumTypeOrBuilderList(){ return null; } + public List getExtensionOrBuilderList(){ return null; } + public List getServiceOrBuilderList(){ return null; } + public List getMessageTypeList(){ return null; } + public List getEnumTypeList(){ return null; } + public List getExtensionList(){ return null; } + public List getServiceList(){ return null; } + public List getPublicDependencyList(){ return null; } + public List getWeakDependencyList(){ return null; } + public List getDependencyList(){ return null; } + public String getDependency(int p0){ return null; } + public String getName(){ return null; } + public String getPackage(){ return null; } + public boolean hasName(){ return false; } + public boolean hasOptions(){ return false; } + public boolean hasPackage(){ return false; } + public boolean hasSourceCodeInfo(){ return false; } + public final boolean isInitialized(){ return false; } + public int getDependencyCount(){ return 0; } + public int getEnumTypeCount(){ return 0; } + public int getExtensionCount(){ return 0; } + public int getMessageTypeCount(){ return 0; } + public int getPublicDependency(int p0){ return 0; } + public int getPublicDependencyCount(){ return 0; } + public int getServiceCount(){ return 0; } + public int getWeakDependency(int p0){ return 0; } + public int getWeakDependencyCount(){ return 0; } + public java.util.List getMessageTypeBuilderList(){ return null; } + public java.util.List getEnumTypeBuilderList(){ return null; } + public java.util.List getExtensionBuilderList(){ return null; } + public java.util.List getServiceBuilderList(){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + } + } + static public class FileOptions extends GeneratedMessage.ExtendableMessage implements DescriptorProtos.FileOptionsOrBuilder + { + protected FileOptions() {} + protected DescriptorProtos.FileOptions.Builder newBuilderForType(GeneratedMessage.BuilderParent p0){ return null; } + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + protected Object writeReplace(){ return null; } + public ByteString getGoPackageBytes(){ return null; } + public ByteString getJavaOuterClassnameBytes(){ return null; } + public ByteString getJavaPackageBytes(){ return null; } + public DescriptorProtos.FileOptions getDefaultInstanceForType(){ return null; } + public DescriptorProtos.FileOptions.Builder newBuilderForType(){ return null; } + public DescriptorProtos.FileOptions.Builder toBuilder(){ return null; } + public DescriptorProtos.FileOptions.OptimizeMode getOptimizeFor(){ return null; } + public DescriptorProtos.UninterpretedOption getUninterpretedOption(int p0){ return null; } + public DescriptorProtos.UninterpretedOptionOrBuilder getUninterpretedOptionOrBuilder(int p0){ return null; } + public List getUninterpretedOptionOrBuilderList(){ return null; } + public List getUninterpretedOptionList(){ return null; } + public Parser getParserForType(){ return null; } + public String getGoPackage(){ return null; } + public String getJavaOuterClassname(){ return null; } + public String getJavaPackage(){ return null; } + public boolean getCcGenericServices(){ return false; } + public boolean getJavaGenerateEqualsAndHash(){ return false; } + public boolean getJavaGenericServices(){ return false; } + public boolean getJavaMultipleFiles(){ return false; } + public boolean getPyGenericServices(){ return false; } + public boolean hasCcGenericServices(){ return false; } + public boolean hasGoPackage(){ return false; } + public boolean hasJavaGenerateEqualsAndHash(){ return false; } + public boolean hasJavaGenericServices(){ return false; } + public boolean hasJavaMultipleFiles(){ return false; } + public boolean hasJavaOuterClassname(){ return false; } + public boolean hasJavaPackage(){ return false; } + public boolean hasOptimizeFor(){ return false; } + public boolean hasPyGenericServices(){ return false; } + public final UnknownFieldSet getUnknownFields(){ return null; } + public final boolean isInitialized(){ return false; } + public int getSerializedSize(){ return 0; } + public int getUninterpretedOptionCount(){ return 0; } + public static DescriptorProtos.FileOptions getDefaultInstance(){ return null; } + public static DescriptorProtos.FileOptions parseDelimitedFrom(InputStream p0){ return null; } + public static DescriptorProtos.FileOptions parseDelimitedFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.FileOptions parseFrom(ByteString p0){ return null; } + public static DescriptorProtos.FileOptions parseFrom(ByteString p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.FileOptions parseFrom(CodedInputStream p0){ return null; } + public static DescriptorProtos.FileOptions parseFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.FileOptions parseFrom(InputStream p0){ return null; } + public static DescriptorProtos.FileOptions parseFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.FileOptions parseFrom(byte[] p0){ return null; } + public static DescriptorProtos.FileOptions parseFrom(byte[] p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.FileOptions.Builder newBuilder(){ return null; } + public static DescriptorProtos.FileOptions.Builder newBuilder(DescriptorProtos.FileOptions p0){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + public static Parser PARSER = null; + public static int CC_GENERIC_SERVICES_FIELD_NUMBER = 0; + public static int GO_PACKAGE_FIELD_NUMBER = 0; + public static int JAVA_GENERATE_EQUALS_AND_HASH_FIELD_NUMBER = 0; + public static int JAVA_GENERIC_SERVICES_FIELD_NUMBER = 0; + public static int JAVA_MULTIPLE_FILES_FIELD_NUMBER = 0; + public static int JAVA_OUTER_CLASSNAME_FIELD_NUMBER = 0; + public static int JAVA_PACKAGE_FIELD_NUMBER = 0; + public static int OPTIMIZE_FOR_FIELD_NUMBER = 0; + public static int PY_GENERIC_SERVICES_FIELD_NUMBER = 0; + public static int UNINTERPRETED_OPTION_FIELD_NUMBER = 0; + public void writeTo(CodedOutputStream p0){} + static public class Builder extends GeneratedMessage.ExtendableBuilder implements DescriptorProtos.FileOptionsOrBuilder + { + protected Builder() {} + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + public ByteString getGoPackageBytes(){ return null; } + public ByteString getJavaOuterClassnameBytes(){ return null; } + public ByteString getJavaPackageBytes(){ return null; } + public DescriptorProtos.FileOptions build(){ return null; } + public DescriptorProtos.FileOptions buildPartial(){ return null; } + public DescriptorProtos.FileOptions getDefaultInstanceForType(){ return null; } + public DescriptorProtos.FileOptions.Builder addAllUninterpretedOption(Iterable p0){ return null; } + public DescriptorProtos.FileOptions.Builder addUninterpretedOption(DescriptorProtos.UninterpretedOption p0){ return null; } + public DescriptorProtos.FileOptions.Builder addUninterpretedOption(DescriptorProtos.UninterpretedOption.Builder p0){ return null; } + public DescriptorProtos.FileOptions.Builder addUninterpretedOption(int p0, DescriptorProtos.UninterpretedOption p1){ return null; } + public DescriptorProtos.FileOptions.Builder addUninterpretedOption(int p0, DescriptorProtos.UninterpretedOption.Builder p1){ return null; } + public DescriptorProtos.FileOptions.Builder clear(){ return null; } + public DescriptorProtos.FileOptions.Builder clearCcGenericServices(){ return null; } + public DescriptorProtos.FileOptions.Builder clearGoPackage(){ return null; } + public DescriptorProtos.FileOptions.Builder clearJavaGenerateEqualsAndHash(){ return null; } + public DescriptorProtos.FileOptions.Builder clearJavaGenericServices(){ return null; } + public DescriptorProtos.FileOptions.Builder clearJavaMultipleFiles(){ return null; } + public DescriptorProtos.FileOptions.Builder clearJavaOuterClassname(){ return null; } + public DescriptorProtos.FileOptions.Builder clearJavaPackage(){ return null; } + public DescriptorProtos.FileOptions.Builder clearOptimizeFor(){ return null; } + public DescriptorProtos.FileOptions.Builder clearPyGenericServices(){ return null; } + public DescriptorProtos.FileOptions.Builder clearUninterpretedOption(){ return null; } + public DescriptorProtos.FileOptions.Builder clone(){ return null; } + public DescriptorProtos.FileOptions.Builder mergeFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public DescriptorProtos.FileOptions.Builder mergeFrom(DescriptorProtos.FileOptions p0){ return null; } + public DescriptorProtos.FileOptions.Builder mergeFrom(Message p0){ return null; } + public DescriptorProtos.FileOptions.Builder removeUninterpretedOption(int p0){ return null; } + public DescriptorProtos.FileOptions.Builder setCcGenericServices(boolean p0){ return null; } + public DescriptorProtos.FileOptions.Builder setGoPackage(String p0){ return null; } + public DescriptorProtos.FileOptions.Builder setGoPackageBytes(ByteString p0){ return null; } + public DescriptorProtos.FileOptions.Builder setJavaGenerateEqualsAndHash(boolean p0){ return null; } + public DescriptorProtos.FileOptions.Builder setJavaGenericServices(boolean p0){ return null; } + public DescriptorProtos.FileOptions.Builder setJavaMultipleFiles(boolean p0){ return null; } + public DescriptorProtos.FileOptions.Builder setJavaOuterClassname(String p0){ return null; } + public DescriptorProtos.FileOptions.Builder setJavaOuterClassnameBytes(ByteString p0){ return null; } + public DescriptorProtos.FileOptions.Builder setJavaPackage(String p0){ return null; } + public DescriptorProtos.FileOptions.Builder setJavaPackageBytes(ByteString p0){ return null; } + public DescriptorProtos.FileOptions.Builder setOptimizeFor(DescriptorProtos.FileOptions.OptimizeMode p0){ return null; } + public DescriptorProtos.FileOptions.Builder setPyGenericServices(boolean p0){ return null; } + public DescriptorProtos.FileOptions.Builder setUninterpretedOption(int p0, DescriptorProtos.UninterpretedOption p1){ return null; } + public DescriptorProtos.FileOptions.Builder setUninterpretedOption(int p0, DescriptorProtos.UninterpretedOption.Builder p1){ return null; } + public DescriptorProtos.FileOptions.OptimizeMode getOptimizeFor(){ return null; } + public DescriptorProtos.UninterpretedOption getUninterpretedOption(int p0){ return null; } + public DescriptorProtos.UninterpretedOption.Builder addUninterpretedOptionBuilder(){ return null; } + public DescriptorProtos.UninterpretedOption.Builder addUninterpretedOptionBuilder(int p0){ return null; } + public DescriptorProtos.UninterpretedOption.Builder getUninterpretedOptionBuilder(int p0){ return null; } + public DescriptorProtos.UninterpretedOptionOrBuilder getUninterpretedOptionOrBuilder(int p0){ return null; } + public Descriptors.Descriptor getDescriptorForType(){ return null; } + public List getUninterpretedOptionOrBuilderList(){ return null; } + public List getUninterpretedOptionList(){ return null; } + public String getGoPackage(){ return null; } + public String getJavaOuterClassname(){ return null; } + public String getJavaPackage(){ return null; } + public boolean getCcGenericServices(){ return false; } + public boolean getJavaGenerateEqualsAndHash(){ return false; } + public boolean getJavaGenericServices(){ return false; } + public boolean getJavaMultipleFiles(){ return false; } + public boolean getPyGenericServices(){ return false; } + public boolean hasCcGenericServices(){ return false; } + public boolean hasGoPackage(){ return false; } + public boolean hasJavaGenerateEqualsAndHash(){ return false; } + public boolean hasJavaGenericServices(){ return false; } + public boolean hasJavaMultipleFiles(){ return false; } + public boolean hasJavaOuterClassname(){ return false; } + public boolean hasJavaPackage(){ return false; } + public boolean hasOptimizeFor(){ return false; } + public boolean hasPyGenericServices(){ return false; } + public final boolean isInitialized(){ return false; } + public int getUninterpretedOptionCount(){ return 0; } + public java.util.List getUninterpretedOptionBuilderList(){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + } + static public enum OptimizeMode implements Internal.EnumLite + { + CODE_SIZE, LITE_RUNTIME, SPEED; + private OptimizeMode() {} + public final Descriptors.EnumDescriptor getDescriptorForType(){ return null; } + public final Descriptors.EnumValueDescriptor getValueDescriptor(){ return null; } + public final int getNumber(){ return 0; } + public static Descriptors.EnumDescriptor getDescriptor(){ return null; } + public static Internal.EnumLiteMap internalGetValueMap(){ return null; } + public static int CODE_SIZE_VALUE = 0; + public static int LITE_RUNTIME_VALUE = 0; + public static int SPEED_VALUE = 0; + } + } + static public class MessageOptions extends GeneratedMessage.ExtendableMessage implements DescriptorProtos.MessageOptionsOrBuilder + { + protected MessageOptions() {} + protected DescriptorProtos.MessageOptions.Builder newBuilderForType(GeneratedMessage.BuilderParent p0){ return null; } + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + protected Object writeReplace(){ return null; } + public DescriptorProtos.MessageOptions getDefaultInstanceForType(){ return null; } + public DescriptorProtos.MessageOptions.Builder newBuilderForType(){ return null; } + public DescriptorProtos.MessageOptions.Builder toBuilder(){ return null; } + public DescriptorProtos.UninterpretedOption getUninterpretedOption(int p0){ return null; } + public DescriptorProtos.UninterpretedOptionOrBuilder getUninterpretedOptionOrBuilder(int p0){ return null; } + public List getUninterpretedOptionOrBuilderList(){ return null; } + public List getUninterpretedOptionList(){ return null; } + public Parser getParserForType(){ return null; } + public boolean getMessageSetWireFormat(){ return false; } + public boolean getNoStandardDescriptorAccessor(){ return false; } + public boolean hasMessageSetWireFormat(){ return false; } + public boolean hasNoStandardDescriptorAccessor(){ return false; } + public final UnknownFieldSet getUnknownFields(){ return null; } + public final boolean isInitialized(){ return false; } + public int getSerializedSize(){ return 0; } + public int getUninterpretedOptionCount(){ return 0; } + public static DescriptorProtos.MessageOptions getDefaultInstance(){ return null; } + public static DescriptorProtos.MessageOptions parseDelimitedFrom(InputStream p0){ return null; } + public static DescriptorProtos.MessageOptions parseDelimitedFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.MessageOptions parseFrom(ByteString p0){ return null; } + public static DescriptorProtos.MessageOptions parseFrom(ByteString p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.MessageOptions parseFrom(CodedInputStream p0){ return null; } + public static DescriptorProtos.MessageOptions parseFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.MessageOptions parseFrom(InputStream p0){ return null; } + public static DescriptorProtos.MessageOptions parseFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.MessageOptions parseFrom(byte[] p0){ return null; } + public static DescriptorProtos.MessageOptions parseFrom(byte[] p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.MessageOptions.Builder newBuilder(){ return null; } + public static DescriptorProtos.MessageOptions.Builder newBuilder(DescriptorProtos.MessageOptions p0){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + public static Parser PARSER = null; + public static int MESSAGE_SET_WIRE_FORMAT_FIELD_NUMBER = 0; + public static int NO_STANDARD_DESCRIPTOR_ACCESSOR_FIELD_NUMBER = 0; + public static int UNINTERPRETED_OPTION_FIELD_NUMBER = 0; + public void writeTo(CodedOutputStream p0){} + static public class Builder extends GeneratedMessage.ExtendableBuilder implements DescriptorProtos.MessageOptionsOrBuilder + { + protected Builder() {} + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + public DescriptorProtos.MessageOptions build(){ return null; } + public DescriptorProtos.MessageOptions buildPartial(){ return null; } + public DescriptorProtos.MessageOptions getDefaultInstanceForType(){ return null; } + public DescriptorProtos.MessageOptions.Builder addAllUninterpretedOption(Iterable p0){ return null; } + public DescriptorProtos.MessageOptions.Builder addUninterpretedOption(DescriptorProtos.UninterpretedOption p0){ return null; } + public DescriptorProtos.MessageOptions.Builder addUninterpretedOption(DescriptorProtos.UninterpretedOption.Builder p0){ return null; } + public DescriptorProtos.MessageOptions.Builder addUninterpretedOption(int p0, DescriptorProtos.UninterpretedOption p1){ return null; } + public DescriptorProtos.MessageOptions.Builder addUninterpretedOption(int p0, DescriptorProtos.UninterpretedOption.Builder p1){ return null; } + public DescriptorProtos.MessageOptions.Builder clear(){ return null; } + public DescriptorProtos.MessageOptions.Builder clearMessageSetWireFormat(){ return null; } + public DescriptorProtos.MessageOptions.Builder clearNoStandardDescriptorAccessor(){ return null; } + public DescriptorProtos.MessageOptions.Builder clearUninterpretedOption(){ return null; } + public DescriptorProtos.MessageOptions.Builder clone(){ return null; } + public DescriptorProtos.MessageOptions.Builder mergeFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public DescriptorProtos.MessageOptions.Builder mergeFrom(DescriptorProtos.MessageOptions p0){ return null; } + public DescriptorProtos.MessageOptions.Builder mergeFrom(Message p0){ return null; } + public DescriptorProtos.MessageOptions.Builder removeUninterpretedOption(int p0){ return null; } + public DescriptorProtos.MessageOptions.Builder setMessageSetWireFormat(boolean p0){ return null; } + public DescriptorProtos.MessageOptions.Builder setNoStandardDescriptorAccessor(boolean p0){ return null; } + public DescriptorProtos.MessageOptions.Builder setUninterpretedOption(int p0, DescriptorProtos.UninterpretedOption p1){ return null; } + public DescriptorProtos.MessageOptions.Builder setUninterpretedOption(int p0, DescriptorProtos.UninterpretedOption.Builder p1){ return null; } + public DescriptorProtos.UninterpretedOption getUninterpretedOption(int p0){ return null; } + public DescriptorProtos.UninterpretedOption.Builder addUninterpretedOptionBuilder(){ return null; } + public DescriptorProtos.UninterpretedOption.Builder addUninterpretedOptionBuilder(int p0){ return null; } + public DescriptorProtos.UninterpretedOption.Builder getUninterpretedOptionBuilder(int p0){ return null; } + public DescriptorProtos.UninterpretedOptionOrBuilder getUninterpretedOptionOrBuilder(int p0){ return null; } + public Descriptors.Descriptor getDescriptorForType(){ return null; } + public List getUninterpretedOptionOrBuilderList(){ return null; } + public List getUninterpretedOptionList(){ return null; } + public boolean getMessageSetWireFormat(){ return false; } + public boolean getNoStandardDescriptorAccessor(){ return false; } + public boolean hasMessageSetWireFormat(){ return false; } + public boolean hasNoStandardDescriptorAccessor(){ return false; } + public final boolean isInitialized(){ return false; } + public int getUninterpretedOptionCount(){ return 0; } + public java.util.List getUninterpretedOptionBuilderList(){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + } + } + static public class MethodDescriptorProto extends GeneratedMessage implements DescriptorProtos.MethodDescriptorProtoOrBuilder + { + protected MethodDescriptorProto() {} + protected DescriptorProtos.MethodDescriptorProto.Builder newBuilderForType(GeneratedMessage.BuilderParent p0){ return null; } + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + protected Object writeReplace(){ return null; } + public ByteString getInputTypeBytes(){ return null; } + public ByteString getNameBytes(){ return null; } + public ByteString getOutputTypeBytes(){ return null; } + public DescriptorProtos.MethodDescriptorProto getDefaultInstanceForType(){ return null; } + public DescriptorProtos.MethodDescriptorProto.Builder newBuilderForType(){ return null; } + public DescriptorProtos.MethodDescriptorProto.Builder toBuilder(){ return null; } + public DescriptorProtos.MethodOptions getOptions(){ return null; } + public DescriptorProtos.MethodOptionsOrBuilder getOptionsOrBuilder(){ return null; } + public Parser getParserForType(){ return null; } + public String getInputType(){ return null; } + public String getName(){ return null; } + public String getOutputType(){ return null; } + public boolean hasInputType(){ return false; } + public boolean hasName(){ return false; } + public boolean hasOptions(){ return false; } + public boolean hasOutputType(){ return false; } + public final UnknownFieldSet getUnknownFields(){ return null; } + public final boolean isInitialized(){ return false; } + public int getSerializedSize(){ return 0; } + public static DescriptorProtos.MethodDescriptorProto getDefaultInstance(){ return null; } + public static DescriptorProtos.MethodDescriptorProto parseDelimitedFrom(InputStream p0){ return null; } + public static DescriptorProtos.MethodDescriptorProto parseDelimitedFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.MethodDescriptorProto parseFrom(ByteString p0){ return null; } + public static DescriptorProtos.MethodDescriptorProto parseFrom(ByteString p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.MethodDescriptorProto parseFrom(CodedInputStream p0){ return null; } + public static DescriptorProtos.MethodDescriptorProto parseFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.MethodDescriptorProto parseFrom(InputStream p0){ return null; } + public static DescriptorProtos.MethodDescriptorProto parseFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.MethodDescriptorProto parseFrom(byte[] p0){ return null; } + public static DescriptorProtos.MethodDescriptorProto parseFrom(byte[] p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.MethodDescriptorProto.Builder newBuilder(){ return null; } + public static DescriptorProtos.MethodDescriptorProto.Builder newBuilder(DescriptorProtos.MethodDescriptorProto p0){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + public static Parser PARSER = null; + public static int INPUT_TYPE_FIELD_NUMBER = 0; + public static int NAME_FIELD_NUMBER = 0; + public static int OPTIONS_FIELD_NUMBER = 0; + public static int OUTPUT_TYPE_FIELD_NUMBER = 0; + public void writeTo(CodedOutputStream p0){} + static public class Builder extends GeneratedMessage.Builder implements DescriptorProtos.MethodDescriptorProtoOrBuilder + { + protected Builder() {} + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + public ByteString getInputTypeBytes(){ return null; } + public ByteString getNameBytes(){ return null; } + public ByteString getOutputTypeBytes(){ return null; } + public DescriptorProtos.MethodDescriptorProto build(){ return null; } + public DescriptorProtos.MethodDescriptorProto buildPartial(){ return null; } + public DescriptorProtos.MethodDescriptorProto getDefaultInstanceForType(){ return null; } + public DescriptorProtos.MethodDescriptorProto.Builder clear(){ return null; } + public DescriptorProtos.MethodDescriptorProto.Builder clearInputType(){ return null; } + public DescriptorProtos.MethodDescriptorProto.Builder clearName(){ return null; } + public DescriptorProtos.MethodDescriptorProto.Builder clearOptions(){ return null; } + public DescriptorProtos.MethodDescriptorProto.Builder clearOutputType(){ return null; } + public DescriptorProtos.MethodDescriptorProto.Builder clone(){ return null; } + public DescriptorProtos.MethodDescriptorProto.Builder mergeFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public DescriptorProtos.MethodDescriptorProto.Builder mergeFrom(DescriptorProtos.MethodDescriptorProto p0){ return null; } + public DescriptorProtos.MethodDescriptorProto.Builder mergeFrom(Message p0){ return null; } + public DescriptorProtos.MethodDescriptorProto.Builder mergeOptions(DescriptorProtos.MethodOptions p0){ return null; } + public DescriptorProtos.MethodDescriptorProto.Builder setInputType(String p0){ return null; } + public DescriptorProtos.MethodDescriptorProto.Builder setInputTypeBytes(ByteString p0){ return null; } + public DescriptorProtos.MethodDescriptorProto.Builder setName(String p0){ return null; } + public DescriptorProtos.MethodDescriptorProto.Builder setNameBytes(ByteString p0){ return null; } + public DescriptorProtos.MethodDescriptorProto.Builder setOptions(DescriptorProtos.MethodOptions p0){ return null; } + public DescriptorProtos.MethodDescriptorProto.Builder setOptions(DescriptorProtos.MethodOptions.Builder p0){ return null; } + public DescriptorProtos.MethodDescriptorProto.Builder setOutputType(String p0){ return null; } + public DescriptorProtos.MethodDescriptorProto.Builder setOutputTypeBytes(ByteString p0){ return null; } + public DescriptorProtos.MethodOptions getOptions(){ return null; } + public DescriptorProtos.MethodOptions.Builder getOptionsBuilder(){ return null; } + public DescriptorProtos.MethodOptionsOrBuilder getOptionsOrBuilder(){ return null; } + public Descriptors.Descriptor getDescriptorForType(){ return null; } + public String getInputType(){ return null; } + public String getName(){ return null; } + public String getOutputType(){ return null; } + public boolean hasInputType(){ return false; } + public boolean hasName(){ return false; } + public boolean hasOptions(){ return false; } + public boolean hasOutputType(){ return false; } + public final boolean isInitialized(){ return false; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + } + } + static public class MethodOptions extends GeneratedMessage.ExtendableMessage implements DescriptorProtos.MethodOptionsOrBuilder + { + protected MethodOptions() {} + protected DescriptorProtos.MethodOptions.Builder newBuilderForType(GeneratedMessage.BuilderParent p0){ return null; } + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + protected Object writeReplace(){ return null; } + public DescriptorProtos.MethodOptions getDefaultInstanceForType(){ return null; } + public DescriptorProtos.MethodOptions.Builder newBuilderForType(){ return null; } + public DescriptorProtos.MethodOptions.Builder toBuilder(){ return null; } + public DescriptorProtos.UninterpretedOption getUninterpretedOption(int p0){ return null; } + public DescriptorProtos.UninterpretedOptionOrBuilder getUninterpretedOptionOrBuilder(int p0){ return null; } + public List getUninterpretedOptionOrBuilderList(){ return null; } + public List getUninterpretedOptionList(){ return null; } + public Parser getParserForType(){ return null; } + public final UnknownFieldSet getUnknownFields(){ return null; } + public final boolean isInitialized(){ return false; } + public int getSerializedSize(){ return 0; } + public int getUninterpretedOptionCount(){ return 0; } + public static DescriptorProtos.MethodOptions getDefaultInstance(){ return null; } + public static DescriptorProtos.MethodOptions parseDelimitedFrom(InputStream p0){ return null; } + public static DescriptorProtos.MethodOptions parseDelimitedFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.MethodOptions parseFrom(ByteString p0){ return null; } + public static DescriptorProtos.MethodOptions parseFrom(ByteString p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.MethodOptions parseFrom(CodedInputStream p0){ return null; } + public static DescriptorProtos.MethodOptions parseFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.MethodOptions parseFrom(InputStream p0){ return null; } + public static DescriptorProtos.MethodOptions parseFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.MethodOptions parseFrom(byte[] p0){ return null; } + public static DescriptorProtos.MethodOptions parseFrom(byte[] p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.MethodOptions.Builder newBuilder(){ return null; } + public static DescriptorProtos.MethodOptions.Builder newBuilder(DescriptorProtos.MethodOptions p0){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + public static Parser PARSER = null; + public static int UNINTERPRETED_OPTION_FIELD_NUMBER = 0; + public void writeTo(CodedOutputStream p0){} + static public class Builder extends GeneratedMessage.ExtendableBuilder implements DescriptorProtos.MethodOptionsOrBuilder + { + protected Builder() {} + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + public DescriptorProtos.MethodOptions build(){ return null; } + public DescriptorProtos.MethodOptions buildPartial(){ return null; } + public DescriptorProtos.MethodOptions getDefaultInstanceForType(){ return null; } + public DescriptorProtos.MethodOptions.Builder addAllUninterpretedOption(Iterable p0){ return null; } + public DescriptorProtos.MethodOptions.Builder addUninterpretedOption(DescriptorProtos.UninterpretedOption p0){ return null; } + public DescriptorProtos.MethodOptions.Builder addUninterpretedOption(DescriptorProtos.UninterpretedOption.Builder p0){ return null; } + public DescriptorProtos.MethodOptions.Builder addUninterpretedOption(int p0, DescriptorProtos.UninterpretedOption p1){ return null; } + public DescriptorProtos.MethodOptions.Builder addUninterpretedOption(int p0, DescriptorProtos.UninterpretedOption.Builder p1){ return null; } + public DescriptorProtos.MethodOptions.Builder clear(){ return null; } + public DescriptorProtos.MethodOptions.Builder clearUninterpretedOption(){ return null; } + public DescriptorProtos.MethodOptions.Builder clone(){ return null; } + public DescriptorProtos.MethodOptions.Builder mergeFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public DescriptorProtos.MethodOptions.Builder mergeFrom(DescriptorProtos.MethodOptions p0){ return null; } + public DescriptorProtos.MethodOptions.Builder mergeFrom(Message p0){ return null; } + public DescriptorProtos.MethodOptions.Builder removeUninterpretedOption(int p0){ return null; } + public DescriptorProtos.MethodOptions.Builder setUninterpretedOption(int p0, DescriptorProtos.UninterpretedOption p1){ return null; } + public DescriptorProtos.MethodOptions.Builder setUninterpretedOption(int p0, DescriptorProtos.UninterpretedOption.Builder p1){ return null; } + public DescriptorProtos.UninterpretedOption getUninterpretedOption(int p0){ return null; } + public DescriptorProtos.UninterpretedOption.Builder addUninterpretedOptionBuilder(){ return null; } + public DescriptorProtos.UninterpretedOption.Builder addUninterpretedOptionBuilder(int p0){ return null; } + public DescriptorProtos.UninterpretedOption.Builder getUninterpretedOptionBuilder(int p0){ return null; } + public DescriptorProtos.UninterpretedOptionOrBuilder getUninterpretedOptionOrBuilder(int p0){ return null; } + public Descriptors.Descriptor getDescriptorForType(){ return null; } + public List getUninterpretedOptionOrBuilderList(){ return null; } + public List getUninterpretedOptionList(){ return null; } + public final boolean isInitialized(){ return false; } + public int getUninterpretedOptionCount(){ return 0; } + public java.util.List getUninterpretedOptionBuilderList(){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + } + } + static public class ServiceDescriptorProto extends GeneratedMessage implements DescriptorProtos.ServiceDescriptorProtoOrBuilder + { + protected ServiceDescriptorProto() {} + protected DescriptorProtos.ServiceDescriptorProto.Builder newBuilderForType(GeneratedMessage.BuilderParent p0){ return null; } + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + protected Object writeReplace(){ return null; } + public ByteString getNameBytes(){ return null; } + public DescriptorProtos.MethodDescriptorProto getMethod(int p0){ return null; } + public DescriptorProtos.MethodDescriptorProtoOrBuilder getMethodOrBuilder(int p0){ return null; } + public DescriptorProtos.ServiceDescriptorProto getDefaultInstanceForType(){ return null; } + public DescriptorProtos.ServiceDescriptorProto.Builder newBuilderForType(){ return null; } + public DescriptorProtos.ServiceDescriptorProto.Builder toBuilder(){ return null; } + public DescriptorProtos.ServiceOptions getOptions(){ return null; } + public DescriptorProtos.ServiceOptionsOrBuilder getOptionsOrBuilder(){ return null; } + public List getMethodOrBuilderList(){ return null; } + public List getMethodList(){ return null; } + public Parser getParserForType(){ return null; } + public String getName(){ return null; } + public boolean hasName(){ return false; } + public boolean hasOptions(){ return false; } + public final UnknownFieldSet getUnknownFields(){ return null; } + public final boolean isInitialized(){ return false; } + public int getMethodCount(){ return 0; } + public int getSerializedSize(){ return 0; } + public static DescriptorProtos.ServiceDescriptorProto getDefaultInstance(){ return null; } + public static DescriptorProtos.ServiceDescriptorProto parseDelimitedFrom(InputStream p0){ return null; } + public static DescriptorProtos.ServiceDescriptorProto parseDelimitedFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.ServiceDescriptorProto parseFrom(ByteString p0){ return null; } + public static DescriptorProtos.ServiceDescriptorProto parseFrom(ByteString p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.ServiceDescriptorProto parseFrom(CodedInputStream p0){ return null; } + public static DescriptorProtos.ServiceDescriptorProto parseFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.ServiceDescriptorProto parseFrom(InputStream p0){ return null; } + public static DescriptorProtos.ServiceDescriptorProto parseFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.ServiceDescriptorProto parseFrom(byte[] p0){ return null; } + public static DescriptorProtos.ServiceDescriptorProto parseFrom(byte[] p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.ServiceDescriptorProto.Builder newBuilder(){ return null; } + public static DescriptorProtos.ServiceDescriptorProto.Builder newBuilder(DescriptorProtos.ServiceDescriptorProto p0){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + public static Parser PARSER = null; + public static int METHOD_FIELD_NUMBER = 0; + public static int NAME_FIELD_NUMBER = 0; + public static int OPTIONS_FIELD_NUMBER = 0; + public void writeTo(CodedOutputStream p0){} + static public class Builder extends GeneratedMessage.Builder implements DescriptorProtos.ServiceDescriptorProtoOrBuilder + { + protected Builder() {} + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + public ByteString getNameBytes(){ return null; } + public DescriptorProtos.MethodDescriptorProto getMethod(int p0){ return null; } + public DescriptorProtos.MethodDescriptorProto.Builder addMethodBuilder(){ return null; } + public DescriptorProtos.MethodDescriptorProto.Builder addMethodBuilder(int p0){ return null; } + public DescriptorProtos.MethodDescriptorProto.Builder getMethodBuilder(int p0){ return null; } + public DescriptorProtos.MethodDescriptorProtoOrBuilder getMethodOrBuilder(int p0){ return null; } + public DescriptorProtos.ServiceDescriptorProto build(){ return null; } + public DescriptorProtos.ServiceDescriptorProto buildPartial(){ return null; } + public DescriptorProtos.ServiceDescriptorProto getDefaultInstanceForType(){ return null; } + public DescriptorProtos.ServiceDescriptorProto.Builder addAllMethod(Iterable p0){ return null; } + public DescriptorProtos.ServiceDescriptorProto.Builder addMethod(DescriptorProtos.MethodDescriptorProto p0){ return null; } + public DescriptorProtos.ServiceDescriptorProto.Builder addMethod(DescriptorProtos.MethodDescriptorProto.Builder p0){ return null; } + public DescriptorProtos.ServiceDescriptorProto.Builder addMethod(int p0, DescriptorProtos.MethodDescriptorProto p1){ return null; } + public DescriptorProtos.ServiceDescriptorProto.Builder addMethod(int p0, DescriptorProtos.MethodDescriptorProto.Builder p1){ return null; } + public DescriptorProtos.ServiceDescriptorProto.Builder clear(){ return null; } + public DescriptorProtos.ServiceDescriptorProto.Builder clearMethod(){ return null; } + public DescriptorProtos.ServiceDescriptorProto.Builder clearName(){ return null; } + public DescriptorProtos.ServiceDescriptorProto.Builder clearOptions(){ return null; } + public DescriptorProtos.ServiceDescriptorProto.Builder clone(){ return null; } + public DescriptorProtos.ServiceDescriptorProto.Builder mergeFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public DescriptorProtos.ServiceDescriptorProto.Builder mergeFrom(DescriptorProtos.ServiceDescriptorProto p0){ return null; } + public DescriptorProtos.ServiceDescriptorProto.Builder mergeFrom(Message p0){ return null; } + public DescriptorProtos.ServiceDescriptorProto.Builder mergeOptions(DescriptorProtos.ServiceOptions p0){ return null; } + public DescriptorProtos.ServiceDescriptorProto.Builder removeMethod(int p0){ return null; } + public DescriptorProtos.ServiceDescriptorProto.Builder setMethod(int p0, DescriptorProtos.MethodDescriptorProto p1){ return null; } + public DescriptorProtos.ServiceDescriptorProto.Builder setMethod(int p0, DescriptorProtos.MethodDescriptorProto.Builder p1){ return null; } + public DescriptorProtos.ServiceDescriptorProto.Builder setName(String p0){ return null; } + public DescriptorProtos.ServiceDescriptorProto.Builder setNameBytes(ByteString p0){ return null; } + public DescriptorProtos.ServiceDescriptorProto.Builder setOptions(DescriptorProtos.ServiceOptions p0){ return null; } + public DescriptorProtos.ServiceDescriptorProto.Builder setOptions(DescriptorProtos.ServiceOptions.Builder p0){ return null; } + public DescriptorProtos.ServiceOptions getOptions(){ return null; } + public DescriptorProtos.ServiceOptions.Builder getOptionsBuilder(){ return null; } + public DescriptorProtos.ServiceOptionsOrBuilder getOptionsOrBuilder(){ return null; } + public Descriptors.Descriptor getDescriptorForType(){ return null; } + public List getMethodOrBuilderList(){ return null; } + public List getMethodList(){ return null; } + public String getName(){ return null; } + public boolean hasName(){ return false; } + public boolean hasOptions(){ return false; } + public final boolean isInitialized(){ return false; } + public int getMethodCount(){ return 0; } + public java.util.List getMethodBuilderList(){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + } + } + static public class ServiceOptions extends GeneratedMessage.ExtendableMessage implements DescriptorProtos.ServiceOptionsOrBuilder + { + protected ServiceOptions() {} + protected DescriptorProtos.ServiceOptions.Builder newBuilderForType(GeneratedMessage.BuilderParent p0){ return null; } + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + protected Object writeReplace(){ return null; } + public DescriptorProtos.ServiceOptions getDefaultInstanceForType(){ return null; } + public DescriptorProtos.ServiceOptions.Builder newBuilderForType(){ return null; } + public DescriptorProtos.ServiceOptions.Builder toBuilder(){ return null; } + public DescriptorProtos.UninterpretedOption getUninterpretedOption(int p0){ return null; } + public DescriptorProtos.UninterpretedOptionOrBuilder getUninterpretedOptionOrBuilder(int p0){ return null; } + public List getUninterpretedOptionOrBuilderList(){ return null; } + public List getUninterpretedOptionList(){ return null; } + public Parser getParserForType(){ return null; } + public final UnknownFieldSet getUnknownFields(){ return null; } + public final boolean isInitialized(){ return false; } + public int getSerializedSize(){ return 0; } + public int getUninterpretedOptionCount(){ return 0; } + public static DescriptorProtos.ServiceOptions getDefaultInstance(){ return null; } + public static DescriptorProtos.ServiceOptions parseDelimitedFrom(InputStream p0){ return null; } + public static DescriptorProtos.ServiceOptions parseDelimitedFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.ServiceOptions parseFrom(ByteString p0){ return null; } + public static DescriptorProtos.ServiceOptions parseFrom(ByteString p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.ServiceOptions parseFrom(CodedInputStream p0){ return null; } + public static DescriptorProtos.ServiceOptions parseFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.ServiceOptions parseFrom(InputStream p0){ return null; } + public static DescriptorProtos.ServiceOptions parseFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.ServiceOptions parseFrom(byte[] p0){ return null; } + public static DescriptorProtos.ServiceOptions parseFrom(byte[] p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.ServiceOptions.Builder newBuilder(){ return null; } + public static DescriptorProtos.ServiceOptions.Builder newBuilder(DescriptorProtos.ServiceOptions p0){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + public static Parser PARSER = null; + public static int UNINTERPRETED_OPTION_FIELD_NUMBER = 0; + public void writeTo(CodedOutputStream p0){} + static public class Builder extends GeneratedMessage.ExtendableBuilder implements DescriptorProtos.ServiceOptionsOrBuilder + { + protected Builder() {} + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + public DescriptorProtos.ServiceOptions build(){ return null; } + public DescriptorProtos.ServiceOptions buildPartial(){ return null; } + public DescriptorProtos.ServiceOptions getDefaultInstanceForType(){ return null; } + public DescriptorProtos.ServiceOptions.Builder addAllUninterpretedOption(Iterable p0){ return null; } + public DescriptorProtos.ServiceOptions.Builder addUninterpretedOption(DescriptorProtos.UninterpretedOption p0){ return null; } + public DescriptorProtos.ServiceOptions.Builder addUninterpretedOption(DescriptorProtos.UninterpretedOption.Builder p0){ return null; } + public DescriptorProtos.ServiceOptions.Builder addUninterpretedOption(int p0, DescriptorProtos.UninterpretedOption p1){ return null; } + public DescriptorProtos.ServiceOptions.Builder addUninterpretedOption(int p0, DescriptorProtos.UninterpretedOption.Builder p1){ return null; } + public DescriptorProtos.ServiceOptions.Builder clear(){ return null; } + public DescriptorProtos.ServiceOptions.Builder clearUninterpretedOption(){ return null; } + public DescriptorProtos.ServiceOptions.Builder clone(){ return null; } + public DescriptorProtos.ServiceOptions.Builder mergeFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public DescriptorProtos.ServiceOptions.Builder mergeFrom(DescriptorProtos.ServiceOptions p0){ return null; } + public DescriptorProtos.ServiceOptions.Builder mergeFrom(Message p0){ return null; } + public DescriptorProtos.ServiceOptions.Builder removeUninterpretedOption(int p0){ return null; } + public DescriptorProtos.ServiceOptions.Builder setUninterpretedOption(int p0, DescriptorProtos.UninterpretedOption p1){ return null; } + public DescriptorProtos.ServiceOptions.Builder setUninterpretedOption(int p0, DescriptorProtos.UninterpretedOption.Builder p1){ return null; } + public DescriptorProtos.UninterpretedOption getUninterpretedOption(int p0){ return null; } + public DescriptorProtos.UninterpretedOption.Builder addUninterpretedOptionBuilder(){ return null; } + public DescriptorProtos.UninterpretedOption.Builder addUninterpretedOptionBuilder(int p0){ return null; } + public DescriptorProtos.UninterpretedOption.Builder getUninterpretedOptionBuilder(int p0){ return null; } + public DescriptorProtos.UninterpretedOptionOrBuilder getUninterpretedOptionOrBuilder(int p0){ return null; } + public Descriptors.Descriptor getDescriptorForType(){ return null; } + public List getUninterpretedOptionOrBuilderList(){ return null; } + public List getUninterpretedOptionList(){ return null; } + public final boolean isInitialized(){ return false; } + public int getUninterpretedOptionCount(){ return 0; } + public java.util.List getUninterpretedOptionBuilderList(){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + } + } + static public class SourceCodeInfo extends GeneratedMessage implements DescriptorProtos.SourceCodeInfoOrBuilder + { + protected SourceCodeInfo() {} + protected DescriptorProtos.SourceCodeInfo.Builder newBuilderForType(GeneratedMessage.BuilderParent p0){ return null; } + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + protected Object writeReplace(){ return null; } + public DescriptorProtos.SourceCodeInfo getDefaultInstanceForType(){ return null; } + public DescriptorProtos.SourceCodeInfo.Builder newBuilderForType(){ return null; } + public DescriptorProtos.SourceCodeInfo.Builder toBuilder(){ return null; } + public DescriptorProtos.SourceCodeInfo.Location getLocation(int p0){ return null; } + public DescriptorProtos.SourceCodeInfo.LocationOrBuilder getLocationOrBuilder(int p0){ return null; } + public List getLocationOrBuilderList(){ return null; } + public List getLocationList(){ return null; } + public Parser getParserForType(){ return null; } + public final UnknownFieldSet getUnknownFields(){ return null; } + public final boolean isInitialized(){ return false; } + public int getLocationCount(){ return 0; } + public int getSerializedSize(){ return 0; } + public static DescriptorProtos.SourceCodeInfo getDefaultInstance(){ return null; } + public static DescriptorProtos.SourceCodeInfo parseDelimitedFrom(InputStream p0){ return null; } + public static DescriptorProtos.SourceCodeInfo parseDelimitedFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.SourceCodeInfo parseFrom(ByteString p0){ return null; } + public static DescriptorProtos.SourceCodeInfo parseFrom(ByteString p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.SourceCodeInfo parseFrom(CodedInputStream p0){ return null; } + public static DescriptorProtos.SourceCodeInfo parseFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.SourceCodeInfo parseFrom(InputStream p0){ return null; } + public static DescriptorProtos.SourceCodeInfo parseFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.SourceCodeInfo parseFrom(byte[] p0){ return null; } + public static DescriptorProtos.SourceCodeInfo parseFrom(byte[] p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.SourceCodeInfo.Builder newBuilder(){ return null; } + public static DescriptorProtos.SourceCodeInfo.Builder newBuilder(DescriptorProtos.SourceCodeInfo p0){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + public static Parser PARSER = null; + public static int LOCATION_FIELD_NUMBER = 0; + public void writeTo(CodedOutputStream p0){} + static public class Builder extends GeneratedMessage.Builder implements DescriptorProtos.SourceCodeInfoOrBuilder + { + protected Builder() {} + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + public DescriptorProtos.SourceCodeInfo build(){ return null; } + public DescriptorProtos.SourceCodeInfo buildPartial(){ return null; } + public DescriptorProtos.SourceCodeInfo getDefaultInstanceForType(){ return null; } + public DescriptorProtos.SourceCodeInfo.Builder addAllLocation(Iterable p0){ return null; } + public DescriptorProtos.SourceCodeInfo.Builder addLocation(DescriptorProtos.SourceCodeInfo.Location p0){ return null; } + public DescriptorProtos.SourceCodeInfo.Builder addLocation(DescriptorProtos.SourceCodeInfo.Location.Builder p0){ return null; } + public DescriptorProtos.SourceCodeInfo.Builder addLocation(int p0, DescriptorProtos.SourceCodeInfo.Location p1){ return null; } + public DescriptorProtos.SourceCodeInfo.Builder addLocation(int p0, DescriptorProtos.SourceCodeInfo.Location.Builder p1){ return null; } + public DescriptorProtos.SourceCodeInfo.Builder clear(){ return null; } + public DescriptorProtos.SourceCodeInfo.Builder clearLocation(){ return null; } + public DescriptorProtos.SourceCodeInfo.Builder clone(){ return null; } + public DescriptorProtos.SourceCodeInfo.Builder mergeFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public DescriptorProtos.SourceCodeInfo.Builder mergeFrom(DescriptorProtos.SourceCodeInfo p0){ return null; } + public DescriptorProtos.SourceCodeInfo.Builder mergeFrom(Message p0){ return null; } + public DescriptorProtos.SourceCodeInfo.Builder removeLocation(int p0){ return null; } + public DescriptorProtos.SourceCodeInfo.Builder setLocation(int p0, DescriptorProtos.SourceCodeInfo.Location p1){ return null; } + public DescriptorProtos.SourceCodeInfo.Builder setLocation(int p0, DescriptorProtos.SourceCodeInfo.Location.Builder p1){ return null; } + public DescriptorProtos.SourceCodeInfo.Location getLocation(int p0){ return null; } + public DescriptorProtos.SourceCodeInfo.Location.Builder addLocationBuilder(){ return null; } + public DescriptorProtos.SourceCodeInfo.Location.Builder addLocationBuilder(int p0){ return null; } + public DescriptorProtos.SourceCodeInfo.Location.Builder getLocationBuilder(int p0){ return null; } + public DescriptorProtos.SourceCodeInfo.LocationOrBuilder getLocationOrBuilder(int p0){ return null; } + public Descriptors.Descriptor getDescriptorForType(){ return null; } + public List getLocationOrBuilderList(){ return null; } + public List getLocationList(){ return null; } + public final boolean isInitialized(){ return false; } + public int getLocationCount(){ return 0; } + public java.util.List getLocationBuilderList(){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + } + static public class Location extends GeneratedMessage implements DescriptorProtos.SourceCodeInfo.LocationOrBuilder + { + protected Location() {} + protected DescriptorProtos.SourceCodeInfo.Location.Builder newBuilderForType(GeneratedMessage.BuilderParent p0){ return null; } + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + protected Object writeReplace(){ return null; } + public ByteString getLeadingCommentsBytes(){ return null; } + public ByteString getTrailingCommentsBytes(){ return null; } + public DescriptorProtos.SourceCodeInfo.Location getDefaultInstanceForType(){ return null; } + public DescriptorProtos.SourceCodeInfo.Location.Builder newBuilderForType(){ return null; } + public DescriptorProtos.SourceCodeInfo.Location.Builder toBuilder(){ return null; } + public List getPathList(){ return null; } + public List getSpanList(){ return null; } + public Parser getParserForType(){ return null; } + public String getLeadingComments(){ return null; } + public String getTrailingComments(){ return null; } + public boolean hasLeadingComments(){ return false; } + public boolean hasTrailingComments(){ return false; } + public final UnknownFieldSet getUnknownFields(){ return null; } + public final boolean isInitialized(){ return false; } + public int getPath(int p0){ return 0; } + public int getPathCount(){ return 0; } + public int getSerializedSize(){ return 0; } + public int getSpan(int p0){ return 0; } + public int getSpanCount(){ return 0; } + public static DescriptorProtos.SourceCodeInfo.Location getDefaultInstance(){ return null; } + public static DescriptorProtos.SourceCodeInfo.Location parseDelimitedFrom(InputStream p0){ return null; } + public static DescriptorProtos.SourceCodeInfo.Location parseDelimitedFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.SourceCodeInfo.Location parseFrom(ByteString p0){ return null; } + public static DescriptorProtos.SourceCodeInfo.Location parseFrom(ByteString p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.SourceCodeInfo.Location parseFrom(CodedInputStream p0){ return null; } + public static DescriptorProtos.SourceCodeInfo.Location parseFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.SourceCodeInfo.Location parseFrom(InputStream p0){ return null; } + public static DescriptorProtos.SourceCodeInfo.Location parseFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.SourceCodeInfo.Location parseFrom(byte[] p0){ return null; } + public static DescriptorProtos.SourceCodeInfo.Location parseFrom(byte[] p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.SourceCodeInfo.Location.Builder newBuilder(){ return null; } + public static DescriptorProtos.SourceCodeInfo.Location.Builder newBuilder(DescriptorProtos.SourceCodeInfo.Location p0){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + public static Parser PARSER = null; + public static int LEADING_COMMENTS_FIELD_NUMBER = 0; + public static int PATH_FIELD_NUMBER = 0; + public static int SPAN_FIELD_NUMBER = 0; + public static int TRAILING_COMMENTS_FIELD_NUMBER = 0; + public void writeTo(CodedOutputStream p0){} + static public class Builder extends GeneratedMessage.Builder implements DescriptorProtos.SourceCodeInfo.LocationOrBuilder + { + protected Builder() {} + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + public ByteString getLeadingCommentsBytes(){ return null; } + public ByteString getTrailingCommentsBytes(){ return null; } + public DescriptorProtos.SourceCodeInfo.Location build(){ return null; } + public DescriptorProtos.SourceCodeInfo.Location buildPartial(){ return null; } + public DescriptorProtos.SourceCodeInfo.Location getDefaultInstanceForType(){ return null; } + public DescriptorProtos.SourceCodeInfo.Location.Builder addAllPath(Iterable p0){ return null; } + public DescriptorProtos.SourceCodeInfo.Location.Builder addAllSpan(Iterable p0){ return null; } + public DescriptorProtos.SourceCodeInfo.Location.Builder addPath(int p0){ return null; } + public DescriptorProtos.SourceCodeInfo.Location.Builder addSpan(int p0){ return null; } + public DescriptorProtos.SourceCodeInfo.Location.Builder clear(){ return null; } + public DescriptorProtos.SourceCodeInfo.Location.Builder clearLeadingComments(){ return null; } + public DescriptorProtos.SourceCodeInfo.Location.Builder clearPath(){ return null; } + public DescriptorProtos.SourceCodeInfo.Location.Builder clearSpan(){ return null; } + public DescriptorProtos.SourceCodeInfo.Location.Builder clearTrailingComments(){ return null; } + public DescriptorProtos.SourceCodeInfo.Location.Builder clone(){ return null; } + public DescriptorProtos.SourceCodeInfo.Location.Builder mergeFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public DescriptorProtos.SourceCodeInfo.Location.Builder mergeFrom(DescriptorProtos.SourceCodeInfo.Location p0){ return null; } + public DescriptorProtos.SourceCodeInfo.Location.Builder mergeFrom(Message p0){ return null; } + public DescriptorProtos.SourceCodeInfo.Location.Builder setLeadingComments(String p0){ return null; } + public DescriptorProtos.SourceCodeInfo.Location.Builder setLeadingCommentsBytes(ByteString p0){ return null; } + public DescriptorProtos.SourceCodeInfo.Location.Builder setPath(int p0, int p1){ return null; } + public DescriptorProtos.SourceCodeInfo.Location.Builder setSpan(int p0, int p1){ return null; } + public DescriptorProtos.SourceCodeInfo.Location.Builder setTrailingComments(String p0){ return null; } + public DescriptorProtos.SourceCodeInfo.Location.Builder setTrailingCommentsBytes(ByteString p0){ return null; } + public Descriptors.Descriptor getDescriptorForType(){ return null; } + public List getPathList(){ return null; } + public List getSpanList(){ return null; } + public String getLeadingComments(){ return null; } + public String getTrailingComments(){ return null; } + public boolean hasLeadingComments(){ return false; } + public boolean hasTrailingComments(){ return false; } + public final boolean isInitialized(){ return false; } + public int getPath(int p0){ return 0; } + public int getPathCount(){ return 0; } + public int getSpan(int p0){ return 0; } + public int getSpanCount(){ return 0; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + } + } + static public interface LocationOrBuilder extends MessageOrBuilder + { + ByteString getLeadingCommentsBytes(); + ByteString getTrailingCommentsBytes(); + List getPathList(); + List getSpanList(); + String getLeadingComments(); + String getTrailingComments(); + boolean hasLeadingComments(); + boolean hasTrailingComments(); + int getPath(int p0); + int getPathCount(); + int getSpan(int p0); + int getSpanCount(); + } + } + static public class UninterpretedOption extends GeneratedMessage implements DescriptorProtos.UninterpretedOptionOrBuilder + { + protected UninterpretedOption() {} + protected DescriptorProtos.UninterpretedOption.Builder newBuilderForType(GeneratedMessage.BuilderParent p0){ return null; } + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + protected Object writeReplace(){ return null; } + public ByteString getAggregateValueBytes(){ return null; } + public ByteString getIdentifierValueBytes(){ return null; } + public ByteString getStringValue(){ return null; } + public DescriptorProtos.UninterpretedOption getDefaultInstanceForType(){ return null; } + public DescriptorProtos.UninterpretedOption.Builder newBuilderForType(){ return null; } + public DescriptorProtos.UninterpretedOption.Builder toBuilder(){ return null; } + public DescriptorProtos.UninterpretedOption.NamePart getName(int p0){ return null; } + public DescriptorProtos.UninterpretedOption.NamePartOrBuilder getNameOrBuilder(int p0){ return null; } + public List getNameOrBuilderList(){ return null; } + public List getNameList(){ return null; } + public Parser getParserForType(){ return null; } + public String getAggregateValue(){ return null; } + public String getIdentifierValue(){ return null; } + public boolean hasAggregateValue(){ return false; } + public boolean hasDoubleValue(){ return false; } + public boolean hasIdentifierValue(){ return false; } + public boolean hasNegativeIntValue(){ return false; } + public boolean hasPositiveIntValue(){ return false; } + public boolean hasStringValue(){ return false; } + public double getDoubleValue(){ return 0; } + public final UnknownFieldSet getUnknownFields(){ return null; } + public final boolean isInitialized(){ return false; } + public int getNameCount(){ return 0; } + public int getSerializedSize(){ return 0; } + public long getNegativeIntValue(){ return 0; } + public long getPositiveIntValue(){ return 0; } + public static DescriptorProtos.UninterpretedOption getDefaultInstance(){ return null; } + public static DescriptorProtos.UninterpretedOption parseDelimitedFrom(InputStream p0){ return null; } + public static DescriptorProtos.UninterpretedOption parseDelimitedFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.UninterpretedOption parseFrom(ByteString p0){ return null; } + public static DescriptorProtos.UninterpretedOption parseFrom(ByteString p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.UninterpretedOption parseFrom(CodedInputStream p0){ return null; } + public static DescriptorProtos.UninterpretedOption parseFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.UninterpretedOption parseFrom(InputStream p0){ return null; } + public static DescriptorProtos.UninterpretedOption parseFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.UninterpretedOption parseFrom(byte[] p0){ return null; } + public static DescriptorProtos.UninterpretedOption parseFrom(byte[] p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.UninterpretedOption.Builder newBuilder(){ return null; } + public static DescriptorProtos.UninterpretedOption.Builder newBuilder(DescriptorProtos.UninterpretedOption p0){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + public static Parser PARSER = null; + public static int AGGREGATE_VALUE_FIELD_NUMBER = 0; + public static int DOUBLE_VALUE_FIELD_NUMBER = 0; + public static int IDENTIFIER_VALUE_FIELD_NUMBER = 0; + public static int NAME_FIELD_NUMBER = 0; + public static int NEGATIVE_INT_VALUE_FIELD_NUMBER = 0; + public static int POSITIVE_INT_VALUE_FIELD_NUMBER = 0; + public static int STRING_VALUE_FIELD_NUMBER = 0; + public void writeTo(CodedOutputStream p0){} + static public class Builder extends GeneratedMessage.Builder implements DescriptorProtos.UninterpretedOptionOrBuilder + { + protected Builder() {} + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + public ByteString getAggregateValueBytes(){ return null; } + public ByteString getIdentifierValueBytes(){ return null; } + public ByteString getStringValue(){ return null; } + public DescriptorProtos.UninterpretedOption build(){ return null; } + public DescriptorProtos.UninterpretedOption buildPartial(){ return null; } + public DescriptorProtos.UninterpretedOption getDefaultInstanceForType(){ return null; } + public DescriptorProtos.UninterpretedOption.Builder addAllName(Iterable p0){ return null; } + public DescriptorProtos.UninterpretedOption.Builder addName(DescriptorProtos.UninterpretedOption.NamePart p0){ return null; } + public DescriptorProtos.UninterpretedOption.Builder addName(DescriptorProtos.UninterpretedOption.NamePart.Builder p0){ return null; } + public DescriptorProtos.UninterpretedOption.Builder addName(int p0, DescriptorProtos.UninterpretedOption.NamePart p1){ return null; } + public DescriptorProtos.UninterpretedOption.Builder addName(int p0, DescriptorProtos.UninterpretedOption.NamePart.Builder p1){ return null; } + public DescriptorProtos.UninterpretedOption.Builder clear(){ return null; } + public DescriptorProtos.UninterpretedOption.Builder clearAggregateValue(){ return null; } + public DescriptorProtos.UninterpretedOption.Builder clearDoubleValue(){ return null; } + public DescriptorProtos.UninterpretedOption.Builder clearIdentifierValue(){ return null; } + public DescriptorProtos.UninterpretedOption.Builder clearName(){ return null; } + public DescriptorProtos.UninterpretedOption.Builder clearNegativeIntValue(){ return null; } + public DescriptorProtos.UninterpretedOption.Builder clearPositiveIntValue(){ return null; } + public DescriptorProtos.UninterpretedOption.Builder clearStringValue(){ return null; } + public DescriptorProtos.UninterpretedOption.Builder clone(){ return null; } + public DescriptorProtos.UninterpretedOption.Builder mergeFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public DescriptorProtos.UninterpretedOption.Builder mergeFrom(DescriptorProtos.UninterpretedOption p0){ return null; } + public DescriptorProtos.UninterpretedOption.Builder mergeFrom(Message p0){ return null; } + public DescriptorProtos.UninterpretedOption.Builder removeName(int p0){ return null; } + public DescriptorProtos.UninterpretedOption.Builder setAggregateValue(String p0){ return null; } + public DescriptorProtos.UninterpretedOption.Builder setAggregateValueBytes(ByteString p0){ return null; } + public DescriptorProtos.UninterpretedOption.Builder setDoubleValue(double p0){ return null; } + public DescriptorProtos.UninterpretedOption.Builder setIdentifierValue(String p0){ return null; } + public DescriptorProtos.UninterpretedOption.Builder setIdentifierValueBytes(ByteString p0){ return null; } + public DescriptorProtos.UninterpretedOption.Builder setName(int p0, DescriptorProtos.UninterpretedOption.NamePart p1){ return null; } + public DescriptorProtos.UninterpretedOption.Builder setName(int p0, DescriptorProtos.UninterpretedOption.NamePart.Builder p1){ return null; } + public DescriptorProtos.UninterpretedOption.Builder setNegativeIntValue(long p0){ return null; } + public DescriptorProtos.UninterpretedOption.Builder setPositiveIntValue(long p0){ return null; } + public DescriptorProtos.UninterpretedOption.Builder setStringValue(ByteString p0){ return null; } + public DescriptorProtos.UninterpretedOption.NamePart getName(int p0){ return null; } + public DescriptorProtos.UninterpretedOption.NamePart.Builder addNameBuilder(){ return null; } + public DescriptorProtos.UninterpretedOption.NamePart.Builder addNameBuilder(int p0){ return null; } + public DescriptorProtos.UninterpretedOption.NamePart.Builder getNameBuilder(int p0){ return null; } + public DescriptorProtos.UninterpretedOption.NamePartOrBuilder getNameOrBuilder(int p0){ return null; } + public Descriptors.Descriptor getDescriptorForType(){ return null; } + public List getNameOrBuilderList(){ return null; } + public List getNameList(){ return null; } + public String getAggregateValue(){ return null; } + public String getIdentifierValue(){ return null; } + public boolean hasAggregateValue(){ return false; } + public boolean hasDoubleValue(){ return false; } + public boolean hasIdentifierValue(){ return false; } + public boolean hasNegativeIntValue(){ return false; } + public boolean hasPositiveIntValue(){ return false; } + public boolean hasStringValue(){ return false; } + public double getDoubleValue(){ return 0; } + public final boolean isInitialized(){ return false; } + public int getNameCount(){ return 0; } + public java.util.List getNameBuilderList(){ return null; } + public long getNegativeIntValue(){ return 0; } + public long getPositiveIntValue(){ return 0; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + } + static public class NamePart extends GeneratedMessage implements DescriptorProtos.UninterpretedOption.NamePartOrBuilder + { + protected NamePart() {} + protected DescriptorProtos.UninterpretedOption.NamePart.Builder newBuilderForType(GeneratedMessage.BuilderParent p0){ return null; } + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + protected Object writeReplace(){ return null; } + public ByteString getNamePartBytes(){ return null; } + public DescriptorProtos.UninterpretedOption.NamePart getDefaultInstanceForType(){ return null; } + public DescriptorProtos.UninterpretedOption.NamePart.Builder newBuilderForType(){ return null; } + public DescriptorProtos.UninterpretedOption.NamePart.Builder toBuilder(){ return null; } + public Parser getParserForType(){ return null; } + public String getNamePart(){ return null; } + public boolean getIsExtension(){ return false; } + public boolean hasIsExtension(){ return false; } + public boolean hasNamePart(){ return false; } + public final UnknownFieldSet getUnknownFields(){ return null; } + public final boolean isInitialized(){ return false; } + public int getSerializedSize(){ return 0; } + public static DescriptorProtos.UninterpretedOption.NamePart getDefaultInstance(){ return null; } + public static DescriptorProtos.UninterpretedOption.NamePart parseDelimitedFrom(InputStream p0){ return null; } + public static DescriptorProtos.UninterpretedOption.NamePart parseDelimitedFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.UninterpretedOption.NamePart parseFrom(ByteString p0){ return null; } + public static DescriptorProtos.UninterpretedOption.NamePart parseFrom(ByteString p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.UninterpretedOption.NamePart parseFrom(CodedInputStream p0){ return null; } + public static DescriptorProtos.UninterpretedOption.NamePart parseFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.UninterpretedOption.NamePart parseFrom(InputStream p0){ return null; } + public static DescriptorProtos.UninterpretedOption.NamePart parseFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.UninterpretedOption.NamePart parseFrom(byte[] p0){ return null; } + public static DescriptorProtos.UninterpretedOption.NamePart parseFrom(byte[] p0, ExtensionRegistryLite p1){ return null; } + public static DescriptorProtos.UninterpretedOption.NamePart.Builder newBuilder(){ return null; } + public static DescriptorProtos.UninterpretedOption.NamePart.Builder newBuilder(DescriptorProtos.UninterpretedOption.NamePart p0){ return null; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + public static Parser PARSER = null; + public static int IS_EXTENSION_FIELD_NUMBER = 0; + public static int NAME_PART_FIELD_NUMBER = 0; + public void writeTo(CodedOutputStream p0){} + static public class Builder extends GeneratedMessage.Builder implements DescriptorProtos.UninterpretedOption.NamePartOrBuilder + { + protected Builder() {} + protected GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(){ return null; } + public ByteString getNamePartBytes(){ return null; } + public DescriptorProtos.UninterpretedOption.NamePart build(){ return null; } + public DescriptorProtos.UninterpretedOption.NamePart buildPartial(){ return null; } + public DescriptorProtos.UninterpretedOption.NamePart getDefaultInstanceForType(){ return null; } + public DescriptorProtos.UninterpretedOption.NamePart.Builder clear(){ return null; } + public DescriptorProtos.UninterpretedOption.NamePart.Builder clearIsExtension(){ return null; } + public DescriptorProtos.UninterpretedOption.NamePart.Builder clearNamePart(){ return null; } + public DescriptorProtos.UninterpretedOption.NamePart.Builder clone(){ return null; } + public DescriptorProtos.UninterpretedOption.NamePart.Builder mergeFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public DescriptorProtos.UninterpretedOption.NamePart.Builder mergeFrom(DescriptorProtos.UninterpretedOption.NamePart p0){ return null; } + public DescriptorProtos.UninterpretedOption.NamePart.Builder mergeFrom(Message p0){ return null; } + public DescriptorProtos.UninterpretedOption.NamePart.Builder setIsExtension(boolean p0){ return null; } + public DescriptorProtos.UninterpretedOption.NamePart.Builder setNamePart(String p0){ return null; } + public DescriptorProtos.UninterpretedOption.NamePart.Builder setNamePartBytes(ByteString p0){ return null; } + public Descriptors.Descriptor getDescriptorForType(){ return null; } + public String getNamePart(){ return null; } + public boolean getIsExtension(){ return false; } + public boolean hasIsExtension(){ return false; } + public boolean hasNamePart(){ return false; } + public final boolean isInitialized(){ return false; } + public static Descriptors.Descriptor getDescriptor(){ return null; } + } + } + static public interface NamePartOrBuilder extends MessageOrBuilder + { + ByteString getNamePartBytes(); + String getNamePart(); + boolean getIsExtension(); + boolean hasIsExtension(); + boolean hasNamePart(); + } + } + static public interface DescriptorProtoOrBuilder extends MessageOrBuilder + { + ByteString getNameBytes(); + DescriptorProtos.DescriptorProto getNestedType(int p0); + DescriptorProtos.DescriptorProto.ExtensionRange getExtensionRange(int p0); + DescriptorProtos.DescriptorProto.ExtensionRangeOrBuilder getExtensionRangeOrBuilder(int p0); + DescriptorProtos.DescriptorProtoOrBuilder getNestedTypeOrBuilder(int p0); + DescriptorProtos.EnumDescriptorProto getEnumType(int p0); + DescriptorProtos.EnumDescriptorProtoOrBuilder getEnumTypeOrBuilder(int p0); + DescriptorProtos.FieldDescriptorProto getExtension(int p0); + DescriptorProtos.FieldDescriptorProto getField(int p0); + DescriptorProtos.FieldDescriptorProtoOrBuilder getExtensionOrBuilder(int p0); + DescriptorProtos.FieldDescriptorProtoOrBuilder getFieldOrBuilder(int p0); + DescriptorProtos.MessageOptions getOptions(); + DescriptorProtos.MessageOptionsOrBuilder getOptionsOrBuilder(); + List getExtensionRangeOrBuilderList(); + List getNestedTypeOrBuilderList(); + List getEnumTypeOrBuilderList(); + List getExtensionOrBuilderList(); + List getFieldOrBuilderList(); + List getExtensionRangeList(); + List getNestedTypeList(); + List getEnumTypeList(); + List getExtensionList(); + List getFieldList(); + String getName(); + boolean hasName(); + boolean hasOptions(); + int getEnumTypeCount(); + int getExtensionCount(); + int getExtensionRangeCount(); + int getFieldCount(); + int getNestedTypeCount(); + } + static public interface EnumDescriptorProtoOrBuilder extends MessageOrBuilder + { + ByteString getNameBytes(); + DescriptorProtos.EnumOptions getOptions(); + DescriptorProtos.EnumOptionsOrBuilder getOptionsOrBuilder(); + DescriptorProtos.EnumValueDescriptorProto getValue(int p0); + DescriptorProtos.EnumValueDescriptorProtoOrBuilder getValueOrBuilder(int p0); + List getValueOrBuilderList(); + List getValueList(); + String getName(); + boolean hasName(); + boolean hasOptions(); + int getValueCount(); + } + static public interface EnumOptionsOrBuilder extends GeneratedMessage.ExtendableMessageOrBuilder + { + DescriptorProtos.UninterpretedOption getUninterpretedOption(int p0); + DescriptorProtos.UninterpretedOptionOrBuilder getUninterpretedOptionOrBuilder(int p0); + List getUninterpretedOptionOrBuilderList(); + List getUninterpretedOptionList(); + boolean getAllowAlias(); + boolean hasAllowAlias(); + int getUninterpretedOptionCount(); + } + static public interface EnumValueDescriptorProtoOrBuilder extends MessageOrBuilder + { + ByteString getNameBytes(); + DescriptorProtos.EnumValueOptions getOptions(); + DescriptorProtos.EnumValueOptionsOrBuilder getOptionsOrBuilder(); + String getName(); + boolean hasName(); + boolean hasNumber(); + boolean hasOptions(); + int getNumber(); + } + static public interface EnumValueOptionsOrBuilder extends GeneratedMessage.ExtendableMessageOrBuilder + { + DescriptorProtos.UninterpretedOption getUninterpretedOption(int p0); + DescriptorProtos.UninterpretedOptionOrBuilder getUninterpretedOptionOrBuilder(int p0); + List getUninterpretedOptionOrBuilderList(); + List getUninterpretedOptionList(); + int getUninterpretedOptionCount(); + } + static public interface FieldDescriptorProtoOrBuilder extends MessageOrBuilder + { + ByteString getDefaultValueBytes(); + ByteString getExtendeeBytes(); + ByteString getNameBytes(); + ByteString getTypeNameBytes(); + DescriptorProtos.FieldDescriptorProto.Label getLabel(); + DescriptorProtos.FieldDescriptorProto.Type getType(); + DescriptorProtos.FieldOptions getOptions(); + DescriptorProtos.FieldOptionsOrBuilder getOptionsOrBuilder(); + String getDefaultValue(); + String getExtendee(); + String getName(); + String getTypeName(); + boolean hasDefaultValue(); + boolean hasExtendee(); + boolean hasLabel(); + boolean hasName(); + boolean hasNumber(); + boolean hasOptions(); + boolean hasType(); + boolean hasTypeName(); + int getNumber(); + } + static public interface FieldOptionsOrBuilder extends GeneratedMessage.ExtendableMessageOrBuilder + { + ByteString getExperimentalMapKeyBytes(); + DescriptorProtos.FieldOptions.CType getCtype(); + DescriptorProtos.UninterpretedOption getUninterpretedOption(int p0); + DescriptorProtos.UninterpretedOptionOrBuilder getUninterpretedOptionOrBuilder(int p0); + List getUninterpretedOptionOrBuilderList(); + List getUninterpretedOptionList(); + String getExperimentalMapKey(); + boolean getDeprecated(); + boolean getLazy(); + boolean getPacked(); + boolean getWeak(); + boolean hasCtype(); + boolean hasDeprecated(); + boolean hasExperimentalMapKey(); + boolean hasLazy(); + boolean hasPacked(); + boolean hasWeak(); + int getUninterpretedOptionCount(); + } + static public interface FileDescriptorProtoOrBuilder extends MessageOrBuilder + { + ByteString getDependencyBytes(int p0); + ByteString getNameBytes(); + ByteString getPackageBytes(); + DescriptorProtos.DescriptorProto getMessageType(int p0); + DescriptorProtos.DescriptorProtoOrBuilder getMessageTypeOrBuilder(int p0); + DescriptorProtos.EnumDescriptorProto getEnumType(int p0); + DescriptorProtos.EnumDescriptorProtoOrBuilder getEnumTypeOrBuilder(int p0); + DescriptorProtos.FieldDescriptorProto getExtension(int p0); + DescriptorProtos.FieldDescriptorProtoOrBuilder getExtensionOrBuilder(int p0); + DescriptorProtos.FileOptions getOptions(); + DescriptorProtos.FileOptionsOrBuilder getOptionsOrBuilder(); + DescriptorProtos.ServiceDescriptorProto getService(int p0); + DescriptorProtos.ServiceDescriptorProtoOrBuilder getServiceOrBuilder(int p0); + DescriptorProtos.SourceCodeInfo getSourceCodeInfo(); + DescriptorProtos.SourceCodeInfoOrBuilder getSourceCodeInfoOrBuilder(); + List getMessageTypeOrBuilderList(); + List getEnumTypeOrBuilderList(); + List getExtensionOrBuilderList(); + List getServiceOrBuilderList(); + List getMessageTypeList(); + List getEnumTypeList(); + List getExtensionList(); + List getServiceList(); + List getPublicDependencyList(); + List getWeakDependencyList(); + List getDependencyList(); + String getDependency(int p0); + String getName(); + String getPackage(); + boolean hasName(); + boolean hasOptions(); + boolean hasPackage(); + boolean hasSourceCodeInfo(); + int getDependencyCount(); + int getEnumTypeCount(); + int getExtensionCount(); + int getMessageTypeCount(); + int getPublicDependency(int p0); + int getPublicDependencyCount(); + int getServiceCount(); + int getWeakDependency(int p0); + int getWeakDependencyCount(); + } + static public interface FileOptionsOrBuilder extends GeneratedMessage.ExtendableMessageOrBuilder + { + ByteString getGoPackageBytes(); + ByteString getJavaOuterClassnameBytes(); + ByteString getJavaPackageBytes(); + DescriptorProtos.FileOptions.OptimizeMode getOptimizeFor(); + DescriptorProtos.UninterpretedOption getUninterpretedOption(int p0); + DescriptorProtos.UninterpretedOptionOrBuilder getUninterpretedOptionOrBuilder(int p0); + List getUninterpretedOptionOrBuilderList(); + List getUninterpretedOptionList(); + String getGoPackage(); + String getJavaOuterClassname(); + String getJavaPackage(); + boolean getCcGenericServices(); + boolean getJavaGenerateEqualsAndHash(); + boolean getJavaGenericServices(); + boolean getJavaMultipleFiles(); + boolean getPyGenericServices(); + boolean hasCcGenericServices(); + boolean hasGoPackage(); + boolean hasJavaGenerateEqualsAndHash(); + boolean hasJavaGenericServices(); + boolean hasJavaMultipleFiles(); + boolean hasJavaOuterClassname(); + boolean hasJavaPackage(); + boolean hasOptimizeFor(); + boolean hasPyGenericServices(); + int getUninterpretedOptionCount(); + } + static public interface MessageOptionsOrBuilder extends GeneratedMessage.ExtendableMessageOrBuilder + { + DescriptorProtos.UninterpretedOption getUninterpretedOption(int p0); + DescriptorProtos.UninterpretedOptionOrBuilder getUninterpretedOptionOrBuilder(int p0); + List getUninterpretedOptionOrBuilderList(); + List getUninterpretedOptionList(); + boolean getMessageSetWireFormat(); + boolean getNoStandardDescriptorAccessor(); + boolean hasMessageSetWireFormat(); + boolean hasNoStandardDescriptorAccessor(); + int getUninterpretedOptionCount(); + } + static public interface MethodDescriptorProtoOrBuilder extends MessageOrBuilder + { + ByteString getInputTypeBytes(); + ByteString getNameBytes(); + ByteString getOutputTypeBytes(); + DescriptorProtos.MethodOptions getOptions(); + DescriptorProtos.MethodOptionsOrBuilder getOptionsOrBuilder(); + String getInputType(); + String getName(); + String getOutputType(); + boolean hasInputType(); + boolean hasName(); + boolean hasOptions(); + boolean hasOutputType(); + } + static public interface MethodOptionsOrBuilder extends GeneratedMessage.ExtendableMessageOrBuilder + { + DescriptorProtos.UninterpretedOption getUninterpretedOption(int p0); + DescriptorProtos.UninterpretedOptionOrBuilder getUninterpretedOptionOrBuilder(int p0); + List getUninterpretedOptionOrBuilderList(); + List getUninterpretedOptionList(); + int getUninterpretedOptionCount(); + } + static public interface ServiceDescriptorProtoOrBuilder extends MessageOrBuilder + { + ByteString getNameBytes(); + DescriptorProtos.MethodDescriptorProto getMethod(int p0); + DescriptorProtos.MethodDescriptorProtoOrBuilder getMethodOrBuilder(int p0); + DescriptorProtos.ServiceOptions getOptions(); + DescriptorProtos.ServiceOptionsOrBuilder getOptionsOrBuilder(); + List getMethodOrBuilderList(); + List getMethodList(); + String getName(); + boolean hasName(); + boolean hasOptions(); + int getMethodCount(); + } + static public interface ServiceOptionsOrBuilder extends GeneratedMessage.ExtendableMessageOrBuilder + { + DescriptorProtos.UninterpretedOption getUninterpretedOption(int p0); + DescriptorProtos.UninterpretedOptionOrBuilder getUninterpretedOptionOrBuilder(int p0); + List getUninterpretedOptionOrBuilderList(); + List getUninterpretedOptionList(); + int getUninterpretedOptionCount(); + } + static public interface SourceCodeInfoOrBuilder extends MessageOrBuilder + { + DescriptorProtos.SourceCodeInfo.Location getLocation(int p0); + DescriptorProtos.SourceCodeInfo.LocationOrBuilder getLocationOrBuilder(int p0); + List getLocationOrBuilderList(); + List getLocationList(); + int getLocationCount(); + } + static public interface UninterpretedOptionOrBuilder extends MessageOrBuilder + { + ByteString getAggregateValueBytes(); + ByteString getIdentifierValueBytes(); + ByteString getStringValue(); + DescriptorProtos.UninterpretedOption.NamePart getName(int p0); + DescriptorProtos.UninterpretedOption.NamePartOrBuilder getNameOrBuilder(int p0); + List getNameOrBuilderList(); + List getNameList(); + String getAggregateValue(); + String getIdentifierValue(); + boolean hasAggregateValue(); + boolean hasDoubleValue(); + boolean hasIdentifierValue(); + boolean hasNegativeIntValue(); + boolean hasPositiveIntValue(); + boolean hasStringValue(); + double getDoubleValue(); + int getNameCount(); + long getNegativeIntValue(); + long getPositiveIntValue(); + } +} diff --git a/java/ql/test/stubs/apache-hive/com/google/protobuf/Descriptors.java b/java/ql/test/stubs/apache-hive/com/google/protobuf/Descriptors.java new file mode 100644 index 00000000000..acb713496f9 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/com/google/protobuf/Descriptors.java @@ -0,0 +1,161 @@ +// Generated automatically from com.google.protobuf.Descriptors for testing purposes + +package com.google.protobuf; + +import com.google.protobuf.DescriptorProtos; +import com.google.protobuf.ExtensionRegistry; +import com.google.protobuf.FieldSet; +import com.google.protobuf.Internal; +import com.google.protobuf.Message; +import com.google.protobuf.MessageLite; +import com.google.protobuf.WireFormat; +import java.util.List; + +public class Descriptors +{ + public interface GenericDescriptor {} + public Descriptors(){} + static public class Descriptor implements Descriptors.GenericDescriptor + { + protected Descriptor() {} + public DescriptorProtos.DescriptorProto toProto(){ return null; } + public DescriptorProtos.MessageOptions getOptions(){ return null; } + public Descriptors.Descriptor findNestedTypeByName(String p0){ return null; } + public Descriptors.Descriptor getContainingType(){ return null; } + public Descriptors.EnumDescriptor findEnumTypeByName(String p0){ return null; } + public Descriptors.FieldDescriptor findFieldByName(String p0){ return null; } + public Descriptors.FieldDescriptor findFieldByNumber(int p0){ return null; } + public Descriptors.FileDescriptor getFile(){ return null; } + public List getNestedTypes(){ return null; } + public List getEnumTypes(){ return null; } + public List getExtensions(){ return null; } + public List getFields(){ return null; } + public String getFullName(){ return null; } + public String getName(){ return null; } + public boolean isExtensionNumber(int p0){ return false; } + public int getIndex(){ return 0; } + } + static public class DescriptorValidationException extends Exception + { + protected DescriptorValidationException() {} + public Message getProblemProto(){ return null; } + public String getDescription(){ return null; } + public String getProblemSymbolName(){ return null; } + } + static public class EnumDescriptor implements Descriptors.GenericDescriptor, Internal.EnumLiteMap + { + protected EnumDescriptor() {} + public DescriptorProtos.EnumDescriptorProto toProto(){ return null; } + public DescriptorProtos.EnumOptions getOptions(){ return null; } + public Descriptors.Descriptor getContainingType(){ return null; } + public Descriptors.EnumValueDescriptor findValueByName(String p0){ return null; } + public Descriptors.EnumValueDescriptor findValueByNumber(int p0){ return null; } + public Descriptors.FileDescriptor getFile(){ return null; } + public List getValues(){ return null; } + public String getFullName(){ return null; } + public String getName(){ return null; } + public int getIndex(){ return 0; } + } + static public class EnumValueDescriptor implements Descriptors.GenericDescriptor, Internal.EnumLite + { + protected EnumValueDescriptor() {} + public DescriptorProtos.EnumValueDescriptorProto toProto(){ return null; } + public DescriptorProtos.EnumValueOptions getOptions(){ return null; } + public Descriptors.EnumDescriptor getType(){ return null; } + public Descriptors.FileDescriptor getFile(){ return null; } + public String getFullName(){ return null; } + public String getName(){ return null; } + public int getIndex(){ return 0; } + public int getNumber(){ return 0; } + } + static public class FieldDescriptor implements Comparable, Descriptors.GenericDescriptor, FieldSet.FieldDescriptorLite + { + protected FieldDescriptor() {} + public DescriptorProtos.FieldDescriptorProto toProto(){ return null; } + public DescriptorProtos.FieldOptions getOptions(){ return null; } + public Descriptors.Descriptor getContainingType(){ return null; } + public Descriptors.Descriptor getExtensionScope(){ return null; } + public Descriptors.Descriptor getMessageType(){ return null; } + public Descriptors.EnumDescriptor getEnumType(){ return null; } + public Descriptors.FieldDescriptor.JavaType getJavaType(){ return null; } + public Descriptors.FieldDescriptor.Type getType(){ return null; } + public Descriptors.FileDescriptor getFile(){ return null; } + public MessageLite.Builder internalMergeFrom(MessageLite.Builder p0, MessageLite p1){ return null; } + public Object getDefaultValue(){ return null; } + public String getFullName(){ return null; } + public String getName(){ return null; } + public WireFormat.FieldType getLiteType(){ return null; } + public WireFormat.JavaType getLiteJavaType(){ return null; } + public boolean hasDefaultValue(){ return false; } + public boolean isExtension(){ return false; } + public boolean isOptional(){ return false; } + public boolean isPackable(){ return false; } + public boolean isPacked(){ return false; } + public boolean isRepeated(){ return false; } + public boolean isRequired(){ return false; } + public int compareTo(Descriptors.FieldDescriptor p0){ return 0; } + public int getIndex(){ return 0; } + public int getNumber(){ return 0; } + static public enum JavaType + { + BOOLEAN, BYTE_STRING, DOUBLE, ENUM, FLOAT, INT, LONG, MESSAGE, STRING; + private JavaType() {} + } + static public enum Type + { + BOOL, BYTES, DOUBLE, ENUM, FIXED32, FIXED64, FLOAT, GROUP, INT32, INT64, MESSAGE, SFIXED32, SFIXED64, SINT32, SINT64, STRING, UINT32, UINT64; + private Type() {} + public DescriptorProtos.FieldDescriptorProto.Type toProto(){ return null; } + public Descriptors.FieldDescriptor.JavaType getJavaType(){ return null; } + } + } + static public class FileDescriptor + { + protected FileDescriptor() {} + public DescriptorProtos.FileDescriptorProto toProto(){ return null; } + public DescriptorProtos.FileOptions getOptions(){ return null; } + public Descriptors.Descriptor findMessageTypeByName(String p0){ return null; } + public Descriptors.EnumDescriptor findEnumTypeByName(String p0){ return null; } + public Descriptors.FieldDescriptor findExtensionByName(String p0){ return null; } + public Descriptors.ServiceDescriptor findServiceByName(String p0){ return null; } + public List getMessageTypes(){ return null; } + public List getEnumTypes(){ return null; } + public List getExtensions(){ return null; } + public List getDependencies(){ return null; } + public List getPublicDependencies(){ return null; } + public List getServices(){ return null; } + public String getName(){ return null; } + public String getPackage(){ return null; } + public static Descriptors.FileDescriptor buildFrom(DescriptorProtos.FileDescriptorProto p0, Descriptors.FileDescriptor[] p1){ return null; } + public static void internalBuildGeneratedFileFrom(String[] p0, Descriptors.FileDescriptor[] p1, Descriptors.FileDescriptor.InternalDescriptorAssigner p2){} + static public interface InternalDescriptorAssigner + { + ExtensionRegistry assignDescriptors(Descriptors.FileDescriptor p0); + } + } + static public class MethodDescriptor implements Descriptors.GenericDescriptor + { + protected MethodDescriptor() {} + public DescriptorProtos.MethodDescriptorProto toProto(){ return null; } + public DescriptorProtos.MethodOptions getOptions(){ return null; } + public Descriptors.Descriptor getInputType(){ return null; } + public Descriptors.Descriptor getOutputType(){ return null; } + public Descriptors.FileDescriptor getFile(){ return null; } + public Descriptors.ServiceDescriptor getService(){ return null; } + public String getFullName(){ return null; } + public String getName(){ return null; } + public int getIndex(){ return 0; } + } + static public class ServiceDescriptor implements Descriptors.GenericDescriptor + { + protected ServiceDescriptor() {} + public DescriptorProtos.ServiceDescriptorProto toProto(){ return null; } + public DescriptorProtos.ServiceOptions getOptions(){ return null; } + public Descriptors.FileDescriptor getFile(){ return null; } + public Descriptors.MethodDescriptor findMethodByName(String p0){ return null; } + public List getMethods(){ return null; } + public String getFullName(){ return null; } + public String getName(){ return null; } + public int getIndex(){ return 0; } + } +} diff --git a/java/ql/test/stubs/apache-hive/com/google/protobuf/ExtensionRegistry.java b/java/ql/test/stubs/apache-hive/com/google/protobuf/ExtensionRegistry.java new file mode 100644 index 00000000000..dee1f45ecac --- /dev/null +++ b/java/ql/test/stubs/apache-hive/com/google/protobuf/ExtensionRegistry.java @@ -0,0 +1,27 @@ +// Generated automatically from com.google.protobuf.ExtensionRegistry for testing purposes + +package com.google.protobuf; + +import com.google.protobuf.Descriptors; +import com.google.protobuf.ExtensionRegistryLite; +import com.google.protobuf.GeneratedMessage; +import com.google.protobuf.Message; + +public class ExtensionRegistry extends ExtensionRegistryLite +{ + protected ExtensionRegistry() {} + public ExtensionRegistry getUnmodifiable(){ return null; } + public ExtensionRegistry.ExtensionInfo findExtensionByName(String p0){ return null; } + public ExtensionRegistry.ExtensionInfo findExtensionByNumber(Descriptors.Descriptor p0, int p1){ return null; } + public static ExtensionRegistry getEmptyRegistry(){ return null; } + public static ExtensionRegistry newInstance(){ return null; } + public void add(Descriptors.FieldDescriptor p0){} + public void add(Descriptors.FieldDescriptor p0, Message p1){} + public void add(GeneratedMessage.GeneratedExtension p0){} + static public class ExtensionInfo + { + protected ExtensionInfo() {} + public final Descriptors.FieldDescriptor descriptor = null; + public final Message defaultInstance = null; + } +} diff --git a/java/ql/test/stubs/apache-hive/com/google/protobuf/ExtensionRegistryLite.java b/java/ql/test/stubs/apache-hive/com/google/protobuf/ExtensionRegistryLite.java new file mode 100644 index 00000000000..f063684ac51 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/com/google/protobuf/ExtensionRegistryLite.java @@ -0,0 +1,17 @@ +// Generated automatically from com.google.protobuf.ExtensionRegistryLite for testing purposes + +package com.google.protobuf; + +import com.google.protobuf.GeneratedMessageLite; +import com.google.protobuf.MessageLite; + +public class ExtensionRegistryLite +{ + public GeneratedMessageLite.GeneratedExtension findLiteExtensionByNumber(ContainingType p0, int p1){ return null; } + public ExtensionRegistryLite getUnmodifiable(){ return null; } + public final void add(GeneratedMessageLite.GeneratedExtension p0){} + public static ExtensionRegistryLite getEmptyRegistry(){ return null; } + public static ExtensionRegistryLite newInstance(){ return null; } + public static boolean isEagerlyParseMessageSets(){ return false; } + public static void setEagerlyParseMessageSets(boolean p0){} +} diff --git a/java/ql/test/stubs/apache-hive/com/google/protobuf/FieldSet.java b/java/ql/test/stubs/apache-hive/com/google/protobuf/FieldSet.java new file mode 100644 index 00000000000..933b2ae3800 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/com/google/protobuf/FieldSet.java @@ -0,0 +1,51 @@ +// Generated automatically from com.google.protobuf.FieldSet for testing purposes + +package com.google.protobuf; + +import com.google.protobuf.CodedInputStream; +import com.google.protobuf.CodedOutputStream; +import com.google.protobuf.Internal; +import com.google.protobuf.MessageLite; +import com.google.protobuf.WireFormat; +import java.util.Iterator; +import java.util.Map; + +class FieldSet> +{ + protected FieldSet() {} + public FieldSet clone(){ return null; } + public Iterator> iterator(){ return null; } + public Map getAllFields(){ return null; } + public Object getField(FieldDescriptorType p0){ return null; } + public Object getRepeatedField(FieldDescriptorType p0, int p1){ return null; } + public boolean hasField(FieldDescriptorType p0){ return false; } + public boolean isImmutable(){ return false; } + public boolean isInitialized(){ return false; } + public int getMessageSetSerializedSize(){ return 0; } + public int getRepeatedFieldCount(FieldDescriptorType p0){ return 0; } + public int getSerializedSize(){ return 0; } + public static > com.google.protobuf.FieldSet emptySet(){ return null; } + public static > com.google.protobuf.FieldSet newFieldSet(){ return null; } + public static Object readPrimitiveField(CodedInputStream p0, WireFormat.FieldType p1){ return null; } + public static int computeFieldSize(FieldSet.FieldDescriptorLite p0, Object p1){ return 0; } + public static void writeField(FieldSet.FieldDescriptorLite p0, Object p1, CodedOutputStream p2){} + public void addRepeatedField(FieldDescriptorType p0, Object p1){} + public void clear(){} + public void clearField(FieldDescriptorType p0){} + public void makeImmutable(){} + public void mergeFrom(FieldSet p0){} + public void setField(FieldDescriptorType p0, Object p1){} + public void setRepeatedField(FieldDescriptorType p0, int p1, Object p2){} + public void writeMessageSetTo(CodedOutputStream p0){} + public void writeTo(CodedOutputStream p0){} + static public interface FieldDescriptorLite> extends java.lang.Comparable + { + Internal.EnumLiteMap getEnumType(); + MessageLite.Builder internalMergeFrom(MessageLite.Builder p0, MessageLite p1); + WireFormat.FieldType getLiteType(); + WireFormat.JavaType getLiteJavaType(); + boolean isPacked(); + boolean isRepeated(); + int getNumber(); + } +} diff --git a/java/ql/test/stubs/apache-hive/com/google/protobuf/GeneratedMessage.java b/java/ql/test/stubs/apache-hive/com/google/protobuf/GeneratedMessage.java new file mode 100644 index 00000000000..6f5c454a36a --- /dev/null +++ b/java/ql/test/stubs/apache-hive/com/google/protobuf/GeneratedMessage.java @@ -0,0 +1,150 @@ +// Generated automatically from com.google.protobuf.GeneratedMessage for testing purposes + +package com.google.protobuf; + +import com.google.protobuf.AbstractMessage; +import com.google.protobuf.CodedInputStream; +import com.google.protobuf.CodedOutputStream; +import com.google.protobuf.Descriptors; +import com.google.protobuf.ExtensionRegistryLite; +import com.google.protobuf.Message; +import com.google.protobuf.MessageOrBuilder; +import com.google.protobuf.Parser; +import com.google.protobuf.UnknownFieldSet; +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +abstract public class GeneratedMessage extends AbstractMessage implements Serializable +{ + abstract static public class Builder extends AbstractMessage.Builder + { + protected Builder(){} + protected Builder(GeneratedMessage.BuilderParent p0){} + protected GeneratedMessage.BuilderParent getParentForChildren(){ return null; } + protected abstract GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(); + protected boolean isClean(){ return false; } + protected boolean parseUnknownField(CodedInputStream p0, UnknownFieldSet.Builder p1, ExtensionRegistryLite p2, int p3){ return false; } + protected final void onChanged(){} + protected void markClean(){} + protected void onBuilt(){} + public BuilderType addRepeatedField(Descriptors.FieldDescriptor p0, Object p1){ return null; } + public BuilderType clear(){ return null; } + public BuilderType clearField(Descriptors.FieldDescriptor p0){ return null; } + public BuilderType clone(){ return null; } + public BuilderType setField(Descriptors.FieldDescriptor p0, Object p1){ return null; } + public BuilderType setRepeatedField(Descriptors.FieldDescriptor p0, int p1, Object p2){ return null; } + public Descriptors.Descriptor getDescriptorForType(){ return null; } + public Map getAllFields(){ return null; } + public Message.Builder getFieldBuilder(Descriptors.FieldDescriptor p0){ return null; } + public Message.Builder newBuilderForField(Descriptors.FieldDescriptor p0){ return null; } + public Object getField(Descriptors.FieldDescriptor p0){ return null; } + public Object getRepeatedField(Descriptors.FieldDescriptor p0, int p1){ return null; } + public boolean hasField(Descriptors.FieldDescriptor p0){ return false; } + public boolean isInitialized(){ return false; } + public final BuilderType mergeUnknownFields(UnknownFieldSet p0){ return null; } + public final BuilderType setUnknownFields(UnknownFieldSet p0){ return null; } + public final UnknownFieldSet getUnknownFields(){ return null; } + public int getRepeatedFieldCount(Descriptors.FieldDescriptor p0){ return 0; } + } + abstract static public class ExtendableBuilder extends GeneratedMessage.Builder implements GeneratedMessage.ExtendableMessageOrBuilder + { + protected ExtendableBuilder(){} + protected ExtendableBuilder(GeneratedMessage.BuilderParent p0){} + protected boolean extensionsAreInitialized(){ return false; } + protected boolean parseUnknownField(CodedInputStream p0, UnknownFieldSet.Builder p1, ExtensionRegistryLite p2, int p3){ return false; } + protected final void mergeExtensionFields(GeneratedMessage.ExtendableMessage p0){} + public BuilderType addRepeatedField(Descriptors.FieldDescriptor p0, Object p1){ return null; } + public BuilderType clear(){ return null; } + public BuilderType clearField(Descriptors.FieldDescriptor p0){ return null; } + public BuilderType clone(){ return null; } + public BuilderType setField(Descriptors.FieldDescriptor p0, Object p1){ return null; } + public BuilderType setRepeatedField(Descriptors.FieldDescriptor p0, int p1, Object p2){ return null; } + public Map getAllFields(){ return null; } + public Object getField(Descriptors.FieldDescriptor p0){ return null; } + public Object getRepeatedField(Descriptors.FieldDescriptor p0, int p1){ return null; } + public boolean hasField(Descriptors.FieldDescriptor p0){ return false; } + public boolean isInitialized(){ return false; } + public final BuilderType addExtension(GeneratedMessage.GeneratedExtension> p0, Type p1){ return null; } + public final BuilderType clearExtension(GeneratedMessage.GeneratedExtension p0){ return null; } + public final BuilderType setExtension(GeneratedMessage.GeneratedExtension p0, Type p1){ return null; } + public final BuilderType setExtension(GeneratedMessage.GeneratedExtension> p0, int p1, Type p2){ return null; } + public final Type getExtension(GeneratedMessage.GeneratedExtension p0){ return null; } + public final Type getExtension(GeneratedMessage.GeneratedExtension> p0, int p1){ return null; } + public final boolean hasExtension(GeneratedMessage.GeneratedExtension p0){ return false; } + public final int getExtensionCount(GeneratedMessage.GeneratedExtension> p0){ return 0; } + public int getRepeatedFieldCount(Descriptors.FieldDescriptor p0){ return 0; } + } + abstract static public class ExtendableMessage extends GeneratedMessage implements GeneratedMessage.ExtendableMessageOrBuilder + { + class ExtensionWriter + { + protected ExtensionWriter() {} + public void writeUntil(int p0, CodedOutputStream p1){} + } + protected ExtendableMessage(){} + protected ExtendableMessage(GeneratedMessage.ExtendableBuilder p0){} + protected GeneratedMessage.ExtendableMessage.ExtensionWriter newExtensionWriter(){ return null; } + protected GeneratedMessage.ExtendableMessage.ExtensionWriter newMessageSetExtensionWriter(){ return null; } + protected Map getExtensionFields(){ return null; } + protected boolean extensionsAreInitialized(){ return false; } + protected boolean parseUnknownField(CodedInputStream p0, UnknownFieldSet.Builder p1, ExtensionRegistryLite p2, int p3){ return false; } + protected int extensionsSerializedSize(){ return 0; } + protected int extensionsSerializedSizeAsMessageSet(){ return 0; } + protected void makeExtensionsImmutable(){} + public Map getAllFields(){ return null; } + public Object getField(Descriptors.FieldDescriptor p0){ return null; } + public Object getRepeatedField(Descriptors.FieldDescriptor p0, int p1){ return null; } + public boolean hasField(Descriptors.FieldDescriptor p0){ return false; } + public boolean isInitialized(){ return false; } + public final Type getExtension(GeneratedMessage.GeneratedExtension p0){ return null; } + public final Type getExtension(GeneratedMessage.GeneratedExtension> p0, int p1){ return null; } + public final boolean hasExtension(GeneratedMessage.GeneratedExtension p0){ return false; } + public final int getExtensionCount(GeneratedMessage.GeneratedExtension> p0){ return 0; } + public int getRepeatedFieldCount(Descriptors.FieldDescriptor p0){ return 0; } + } + protected GeneratedMessage(){} + protected GeneratedMessage(GeneratedMessage.Builder p0){} + protected Object writeReplace(){ return null; } + protected abstract GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable(); + protected abstract Message.Builder newBuilderForType(GeneratedMessage.BuilderParent p0); + protected boolean parseUnknownField(CodedInputStream p0, UnknownFieldSet.Builder p1, ExtensionRegistryLite p2, int p3){ return false; } + protected static boolean alwaysUseFieldBuilders = false; + protected void makeExtensionsImmutable(){} + public Descriptors.Descriptor getDescriptorForType(){ return null; } + public Map getAllFields(){ return null; } + public Object getField(Descriptors.FieldDescriptor p0){ return null; } + public Object getRepeatedField(Descriptors.FieldDescriptor p0, int p1){ return null; } + public Parser getParserForType(){ return null; } + public UnknownFieldSet getUnknownFields(){ return null; } + public boolean hasField(Descriptors.FieldDescriptor p0){ return false; } + public boolean isInitialized(){ return false; } + public int getRepeatedFieldCount(Descriptors.FieldDescriptor p0){ return 0; } + public static GeneratedMessage.GeneratedExtension newFileScopedGeneratedExtension(Class p0, Message p1){ return null; } + public static GeneratedMessage.GeneratedExtension newMessageScopedGeneratedExtension(Message p0, int p1, Class p2, Message p3){ return null; } + public static interface BuilderParent + { + void markDirty(); + } + static public class FieldAccessorTable + { + protected FieldAccessorTable() {} + public FieldAccessorTable(Descriptors.Descriptor p0, String[] p1){} + public FieldAccessorTable(Descriptors.Descriptor p0, String[] p1, Class p2, Class p3){} + public GeneratedMessage.FieldAccessorTable ensureFieldAccessorsInitialized(Class p0, Class p1){ return null; } + } + static public class GeneratedExtension + { + protected GeneratedExtension() {} + public Descriptors.FieldDescriptor getDescriptor(){ return null; } + public Message getMessageDefaultInstance(){ return null; } + public void internalInit(Descriptors.FieldDescriptor p0){} + } + static public interface ExtendableMessageOrBuilder extends MessageOrBuilder + { + Type getExtension(GeneratedMessage.GeneratedExtension p0); + Type getExtension(GeneratedMessage.GeneratedExtension> p0, int p1); + boolean hasExtension(GeneratedMessage.GeneratedExtension p0); + int getExtensionCount(GeneratedMessage.GeneratedExtension> p0); + } +} diff --git a/java/ql/test/stubs/apache-hive/com/google/protobuf/GeneratedMessageLite.java b/java/ql/test/stubs/apache-hive/com/google/protobuf/GeneratedMessageLite.java new file mode 100644 index 00000000000..cad9d6ccb23 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/com/google/protobuf/GeneratedMessageLite.java @@ -0,0 +1,40 @@ +// Generated automatically from com.google.protobuf.GeneratedMessageLite for testing purposes + +package com.google.protobuf; + +import com.google.protobuf.AbstractMessageLite; +import com.google.protobuf.CodedInputStream; +import com.google.protobuf.ExtensionRegistryLite; +import com.google.protobuf.Internal; +import com.google.protobuf.MessageLite; +import com.google.protobuf.Parser; +import com.google.protobuf.WireFormat; +import java.io.Serializable; + +abstract public class GeneratedMessageLite extends AbstractMessageLite implements Serializable +{ + abstract static public class Builder extends AbstractMessageLite.Builder + { + protected Builder(){} + protected boolean parseUnknownField(CodedInputStream p0, ExtensionRegistryLite p1, int p2){ return false; } + public BuilderType clear(){ return null; } + public BuilderType clone(){ return null; } + public abstract BuilderType mergeFrom(MessageType p0); + public abstract MessageType getDefaultInstanceForType(); + } + protected GeneratedMessageLite(){} + protected GeneratedMessageLite(GeneratedMessageLite.Builder p0){} + protected Object writeReplace(){ return null; } + protected boolean parseUnknownField(CodedInputStream p0, ExtensionRegistryLite p1, int p2){ return false; } + protected void makeExtensionsImmutable(){} + public Parser getParserForType(){ return null; } + public static GeneratedMessageLite.GeneratedExtension newRepeatedGeneratedExtension(ContainingType p0, MessageLite p1, Internal.EnumLiteMap p2, int p3, WireFormat.FieldType p4, boolean p5){ return null; } + public static GeneratedMessageLite.GeneratedExtension newSingularGeneratedExtension(ContainingType p0, Type p1, MessageLite p2, Internal.EnumLiteMap p3, int p4, WireFormat.FieldType p5){ return null; } + static public class GeneratedExtension + { + protected GeneratedExtension() {} + public ContainingType getContainingTypeDefaultInstance(){ return null; } + public MessageLite getMessageDefaultInstance(){ return null; } + public int getNumber(){ return 0; } + } +} diff --git a/java/ql/test/stubs/apache-hive/com/google/protobuf/Internal.java b/java/ql/test/stubs/apache-hive/com/google/protobuf/Internal.java new file mode 100644 index 00000000000..15e1a4ab261 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/com/google/protobuf/Internal.java @@ -0,0 +1,21 @@ +// Generated automatically from com.google.protobuf.Internal for testing purposes + +package com.google.protobuf; + +import com.google.protobuf.ByteString; + +public class Internal +{ + public Internal(){} + public static ByteString bytesDefaultValue(String p0){ return null; } + public static String stringDefaultValue(String p0){ return null; } + public static boolean isValidUtf8(ByteString p0){ return false; } + static public interface EnumLite + { + int getNumber(); + } + static public interface EnumLiteMap + { + T findValueByNumber(int p0); + } +} diff --git a/java/ql/test/stubs/apache-hive/com/google/protobuf/InvalidProtocolBufferException.java b/java/ql/test/stubs/apache-hive/com/google/protobuf/InvalidProtocolBufferException.java new file mode 100644 index 00000000000..ada965286ae --- /dev/null +++ b/java/ql/test/stubs/apache-hive/com/google/protobuf/InvalidProtocolBufferException.java @@ -0,0 +1,14 @@ +// Generated automatically from com.google.protobuf.InvalidProtocolBufferException for testing purposes + +package com.google.protobuf; + +import com.google.protobuf.MessageLite; +import java.io.IOException; + +public class InvalidProtocolBufferException extends IOException +{ + protected InvalidProtocolBufferException() {} + public InvalidProtocolBufferException setUnfinishedMessage(MessageLite p0){ return null; } + public InvalidProtocolBufferException(String p0){} + public MessageLite getUnfinishedMessage(){ return null; } +} diff --git a/java/ql/test/stubs/apache-hive/com/google/protobuf/LazyField.java b/java/ql/test/stubs/apache-hive/com/google/protobuf/LazyField.java new file mode 100644 index 00000000000..c15fa9d53e4 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/com/google/protobuf/LazyField.java @@ -0,0 +1,20 @@ +// Generated automatically from com.google.protobuf.LazyField for testing purposes + +package com.google.protobuf; + +import com.google.protobuf.ByteString; +import com.google.protobuf.ExtensionRegistryLite; +import com.google.protobuf.MessageLite; + +class LazyField +{ + protected LazyField() {} + public ByteString toByteString(){ return null; } + public LazyField(MessageLite p0, ExtensionRegistryLite p1, ByteString p2){} + public MessageLite getValue(){ return null; } + public MessageLite setValue(MessageLite p0){ return null; } + public String toString(){ return null; } + public boolean equals(Object p0){ return false; } + public int getSerializedSize(){ return 0; } + public int hashCode(){ return 0; } +} diff --git a/java/ql/test/stubs/apache-hive/com/google/protobuf/Message.java b/java/ql/test/stubs/apache-hive/com/google/protobuf/Message.java new file mode 100644 index 00000000000..4e10c2b77d5 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/com/google/protobuf/Message.java @@ -0,0 +1,52 @@ +// Generated automatically from com.google.protobuf.Message for testing purposes + +package com.google.protobuf; + +import com.google.protobuf.ByteString; +import com.google.protobuf.CodedInputStream; +import com.google.protobuf.Descriptors; +import com.google.protobuf.ExtensionRegistryLite; +import com.google.protobuf.MessageLite; +import com.google.protobuf.MessageOrBuilder; +import com.google.protobuf.Parser; +import com.google.protobuf.UnknownFieldSet; +import java.io.InputStream; + +public interface Message extends MessageLite, MessageOrBuilder +{ + Message.Builder newBuilderForType(); + Message.Builder toBuilder(); + Parser getParserForType(); + String toString(); + boolean equals(Object p0); + int hashCode(); + static public interface Builder extends MessageLite.Builder, MessageOrBuilder + { + Descriptors.Descriptor getDescriptorForType(); + Message build(); + Message buildPartial(); + Message.Builder addRepeatedField(Descriptors.FieldDescriptor p0, Object p1); + Message.Builder clear(); + Message.Builder clearField(Descriptors.FieldDescriptor p0); + Message.Builder clone(); + Message.Builder getFieldBuilder(Descriptors.FieldDescriptor p0); + Message.Builder mergeFrom(ByteString p0); + Message.Builder mergeFrom(ByteString p0, ExtensionRegistryLite p1); + Message.Builder mergeFrom(CodedInputStream p0); + Message.Builder mergeFrom(CodedInputStream p0, ExtensionRegistryLite p1); + Message.Builder mergeFrom(InputStream p0); + Message.Builder mergeFrom(InputStream p0, ExtensionRegistryLite p1); + Message.Builder mergeFrom(Message p0); + Message.Builder mergeFrom(byte[] p0); + Message.Builder mergeFrom(byte[] p0, ExtensionRegistryLite p1); + Message.Builder mergeFrom(byte[] p0, int p1, int p2); + Message.Builder mergeFrom(byte[] p0, int p1, int p2, ExtensionRegistryLite p3); + Message.Builder mergeUnknownFields(UnknownFieldSet p0); + Message.Builder newBuilderForField(Descriptors.FieldDescriptor p0); + Message.Builder setField(Descriptors.FieldDescriptor p0, Object p1); + Message.Builder setRepeatedField(Descriptors.FieldDescriptor p0, int p1, Object p2); + Message.Builder setUnknownFields(UnknownFieldSet p0); + boolean mergeDelimitedFrom(InputStream p0); + boolean mergeDelimitedFrom(InputStream p0, ExtensionRegistryLite p1); + } +} diff --git a/java/ql/test/stubs/apache-hive/com/google/protobuf/MessageLite.java b/java/ql/test/stubs/apache-hive/com/google/protobuf/MessageLite.java new file mode 100644 index 00000000000..a72338b1b6a --- /dev/null +++ b/java/ql/test/stubs/apache-hive/com/google/protobuf/MessageLite.java @@ -0,0 +1,44 @@ +// Generated automatically from com.google.protobuf.MessageLite for testing purposes + +package com.google.protobuf; + +import com.google.protobuf.ByteString; +import com.google.protobuf.CodedInputStream; +import com.google.protobuf.CodedOutputStream; +import com.google.protobuf.ExtensionRegistryLite; +import com.google.protobuf.MessageLiteOrBuilder; +import com.google.protobuf.Parser; +import java.io.InputStream; +import java.io.OutputStream; + +public interface MessageLite extends MessageLiteOrBuilder +{ + ByteString toByteString(); + MessageLite.Builder newBuilderForType(); + MessageLite.Builder toBuilder(); + Parser getParserForType(); + byte[] toByteArray(); + int getSerializedSize(); + static public interface Builder extends Cloneable, MessageLiteOrBuilder + { + MessageLite build(); + MessageLite buildPartial(); + MessageLite.Builder clear(); + MessageLite.Builder clone(); + MessageLite.Builder mergeFrom(ByteString p0); + MessageLite.Builder mergeFrom(ByteString p0, ExtensionRegistryLite p1); + MessageLite.Builder mergeFrom(CodedInputStream p0); + MessageLite.Builder mergeFrom(CodedInputStream p0, ExtensionRegistryLite p1); + MessageLite.Builder mergeFrom(InputStream p0); + MessageLite.Builder mergeFrom(InputStream p0, ExtensionRegistryLite p1); + MessageLite.Builder mergeFrom(byte[] p0); + MessageLite.Builder mergeFrom(byte[] p0, ExtensionRegistryLite p1); + MessageLite.Builder mergeFrom(byte[] p0, int p1, int p2); + MessageLite.Builder mergeFrom(byte[] p0, int p1, int p2, ExtensionRegistryLite p3); + boolean mergeDelimitedFrom(InputStream p0); + boolean mergeDelimitedFrom(InputStream p0, ExtensionRegistryLite p1); + } + void writeDelimitedTo(OutputStream p0); + void writeTo(CodedOutputStream p0); + void writeTo(OutputStream p0); +} diff --git a/java/ql/test/stubs/apache-hive/com/google/protobuf/MessageLiteOrBuilder.java b/java/ql/test/stubs/apache-hive/com/google/protobuf/MessageLiteOrBuilder.java new file mode 100644 index 00000000000..ce394ecf404 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/com/google/protobuf/MessageLiteOrBuilder.java @@ -0,0 +1,11 @@ +// Generated automatically from com.google.protobuf.MessageLiteOrBuilder for testing purposes + +package com.google.protobuf; + +import com.google.protobuf.MessageLite; + +public interface MessageLiteOrBuilder +{ + MessageLite getDefaultInstanceForType(); + boolean isInitialized(); +} diff --git a/java/ql/test/stubs/apache-hive/com/google/protobuf/MessageOrBuilder.java b/java/ql/test/stubs/apache-hive/com/google/protobuf/MessageOrBuilder.java new file mode 100644 index 00000000000..b7f2cfb38d5 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/com/google/protobuf/MessageOrBuilder.java @@ -0,0 +1,24 @@ +// Generated automatically from com.google.protobuf.MessageOrBuilder for testing purposes + +package com.google.protobuf; + +import com.google.protobuf.Descriptors; +import com.google.protobuf.Message; +import com.google.protobuf.MessageLiteOrBuilder; +import com.google.protobuf.UnknownFieldSet; +import java.util.List; +import java.util.Map; + +public interface MessageOrBuilder extends MessageLiteOrBuilder +{ + Descriptors.Descriptor getDescriptorForType(); + List findInitializationErrors(); + Map getAllFields(); + Message getDefaultInstanceForType(); + Object getField(Descriptors.FieldDescriptor p0); + Object getRepeatedField(Descriptors.FieldDescriptor p0, int p1); + String getInitializationErrorString(); + UnknownFieldSet getUnknownFields(); + boolean hasField(Descriptors.FieldDescriptor p0); + int getRepeatedFieldCount(Descriptors.FieldDescriptor p0); +} diff --git a/java/ql/test/stubs/apache-hive/com/google/protobuf/Parser.java b/java/ql/test/stubs/apache-hive/com/google/protobuf/Parser.java new file mode 100644 index 00000000000..ce1d46b122c --- /dev/null +++ b/java/ql/test/stubs/apache-hive/com/google/protobuf/Parser.java @@ -0,0 +1,36 @@ +// Generated automatically from com.google.protobuf.Parser for testing purposes + +package com.google.protobuf; + +import com.google.protobuf.ByteString; +import com.google.protobuf.CodedInputStream; +import com.google.protobuf.ExtensionRegistryLite; +import java.io.InputStream; + +public interface Parser +{ + MessageType parseDelimitedFrom(InputStream p0); + MessageType parseDelimitedFrom(InputStream p0, ExtensionRegistryLite p1); + MessageType parseFrom(ByteString p0); + MessageType parseFrom(ByteString p0, ExtensionRegistryLite p1); + MessageType parseFrom(CodedInputStream p0); + MessageType parseFrom(CodedInputStream p0, ExtensionRegistryLite p1); + MessageType parseFrom(InputStream p0); + MessageType parseFrom(InputStream p0, ExtensionRegistryLite p1); + MessageType parseFrom(byte[] p0); + MessageType parseFrom(byte[] p0, ExtensionRegistryLite p1); + MessageType parseFrom(byte[] p0, int p1, int p2); + MessageType parseFrom(byte[] p0, int p1, int p2, ExtensionRegistryLite p3); + MessageType parsePartialDelimitedFrom(InputStream p0); + MessageType parsePartialDelimitedFrom(InputStream p0, ExtensionRegistryLite p1); + MessageType parsePartialFrom(ByteString p0); + MessageType parsePartialFrom(ByteString p0, ExtensionRegistryLite p1); + MessageType parsePartialFrom(CodedInputStream p0); + MessageType parsePartialFrom(CodedInputStream p0, ExtensionRegistryLite p1); + MessageType parsePartialFrom(InputStream p0); + MessageType parsePartialFrom(InputStream p0, ExtensionRegistryLite p1); + MessageType parsePartialFrom(byte[] p0); + MessageType parsePartialFrom(byte[] p0, ExtensionRegistryLite p1); + MessageType parsePartialFrom(byte[] p0, int p1, int p2); + MessageType parsePartialFrom(byte[] p0, int p1, int p2, ExtensionRegistryLite p3); +} diff --git a/java/ql/test/stubs/apache-hive/com/google/protobuf/ProtocolMessageEnum.java b/java/ql/test/stubs/apache-hive/com/google/protobuf/ProtocolMessageEnum.java new file mode 100644 index 00000000000..799818cdabf --- /dev/null +++ b/java/ql/test/stubs/apache-hive/com/google/protobuf/ProtocolMessageEnum.java @@ -0,0 +1,13 @@ +// Generated automatically from com.google.protobuf.ProtocolMessageEnum for testing purposes + +package com.google.protobuf; + +import com.google.protobuf.Descriptors; +import com.google.protobuf.Internal; + +public interface ProtocolMessageEnum extends Internal.EnumLite +{ + Descriptors.EnumDescriptor getDescriptorForType(); + Descriptors.EnumValueDescriptor getValueDescriptor(); + int getNumber(); +} diff --git a/java/ql/test/stubs/apache-hive/com/google/protobuf/UninitializedMessageException.java b/java/ql/test/stubs/apache-hive/com/google/protobuf/UninitializedMessageException.java new file mode 100644 index 00000000000..bf8a329667d --- /dev/null +++ b/java/ql/test/stubs/apache-hive/com/google/protobuf/UninitializedMessageException.java @@ -0,0 +1,16 @@ +// Generated automatically from com.google.protobuf.UninitializedMessageException for testing purposes + +package com.google.protobuf; + +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.MessageLite; +import java.util.List; + +public class UninitializedMessageException extends RuntimeException +{ + protected UninitializedMessageException() {} + public InvalidProtocolBufferException asInvalidProtocolBufferException(){ return null; } + public List getMissingFields(){ return null; } + public UninitializedMessageException(List p0){} + public UninitializedMessageException(MessageLite p0){} +} diff --git a/java/ql/test/stubs/apache-hive/com/google/protobuf/UnknownFieldSet.java b/java/ql/test/stubs/apache-hive/com/google/protobuf/UnknownFieldSet.java new file mode 100644 index 00000000000..9ac387a44ca --- /dev/null +++ b/java/ql/test/stubs/apache-hive/com/google/protobuf/UnknownFieldSet.java @@ -0,0 +1,109 @@ +// Generated automatically from com.google.protobuf.UnknownFieldSet for testing purposes + +package com.google.protobuf; + +import com.google.protobuf.AbstractParser; +import com.google.protobuf.ByteString; +import com.google.protobuf.CodedInputStream; +import com.google.protobuf.CodedOutputStream; +import com.google.protobuf.ExtensionRegistryLite; +import com.google.protobuf.MessageLite; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; +import java.util.Map; + +public class UnknownFieldSet implements MessageLite +{ + protected UnknownFieldSet() {} + public ByteString toByteString(){ return null; } + public Map asMap(){ return null; } + public String toString(){ return null; } + public UnknownFieldSet getDefaultInstanceForType(){ return null; } + public UnknownFieldSet.Builder newBuilderForType(){ return null; } + public UnknownFieldSet.Builder toBuilder(){ return null; } + public UnknownFieldSet.Field getField(int p0){ return null; } + public boolean equals(Object p0){ return false; } + public boolean hasField(int p0){ return false; } + public boolean isInitialized(){ return false; } + public byte[] toByteArray(){ return null; } + public final UnknownFieldSet.Parser getParserForType(){ return null; } + public int getSerializedSize(){ return 0; } + public int getSerializedSizeAsMessageSet(){ return 0; } + public int hashCode(){ return 0; } + public static UnknownFieldSet getDefaultInstance(){ return null; } + public static UnknownFieldSet parseFrom(ByteString p0){ return null; } + public static UnknownFieldSet parseFrom(CodedInputStream p0){ return null; } + public static UnknownFieldSet parseFrom(InputStream p0){ return null; } + public static UnknownFieldSet parseFrom(byte[] p0){ return null; } + public static UnknownFieldSet.Builder newBuilder(){ return null; } + public static UnknownFieldSet.Builder newBuilder(UnknownFieldSet p0){ return null; } + public void writeAsMessageSetTo(CodedOutputStream p0){} + public void writeDelimitedTo(OutputStream p0){} + public void writeTo(CodedOutputStream p0){} + public void writeTo(OutputStream p0){} + static public class Builder implements MessageLite.Builder + { + protected Builder() {} + public Map asMap(){ return null; } + public UnknownFieldSet build(){ return null; } + public UnknownFieldSet buildPartial(){ return null; } + public UnknownFieldSet getDefaultInstanceForType(){ return null; } + public UnknownFieldSet.Builder addField(int p0, UnknownFieldSet.Field p1){ return null; } + public UnknownFieldSet.Builder clear(){ return null; } + public UnknownFieldSet.Builder clone(){ return null; } + public UnknownFieldSet.Builder mergeField(int p0, UnknownFieldSet.Field p1){ return null; } + public UnknownFieldSet.Builder mergeFrom(ByteString p0){ return null; } + public UnknownFieldSet.Builder mergeFrom(ByteString p0, ExtensionRegistryLite p1){ return null; } + public UnknownFieldSet.Builder mergeFrom(CodedInputStream p0){ return null; } + public UnknownFieldSet.Builder mergeFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + public UnknownFieldSet.Builder mergeFrom(InputStream p0){ return null; } + public UnknownFieldSet.Builder mergeFrom(InputStream p0, ExtensionRegistryLite p1){ return null; } + public UnknownFieldSet.Builder mergeFrom(UnknownFieldSet p0){ return null; } + public UnknownFieldSet.Builder mergeFrom(byte[] p0){ return null; } + public UnknownFieldSet.Builder mergeFrom(byte[] p0, ExtensionRegistryLite p1){ return null; } + public UnknownFieldSet.Builder mergeFrom(byte[] p0, int p1, int p2){ return null; } + public UnknownFieldSet.Builder mergeFrom(byte[] p0, int p1, int p2, ExtensionRegistryLite p3){ return null; } + public UnknownFieldSet.Builder mergeVarintField(int p0, int p1){ return null; } + public boolean hasField(int p0){ return false; } + public boolean isInitialized(){ return false; } + public boolean mergeDelimitedFrom(InputStream p0){ return false; } + public boolean mergeDelimitedFrom(InputStream p0, ExtensionRegistryLite p1){ return false; } + public boolean mergeFieldFrom(int p0, CodedInputStream p1){ return false; } + } + static public class Field + { + protected Field() {} + public List getLengthDelimitedList(){ return null; } + public List getFixed32List(){ return null; } + public List getFixed64List(){ return null; } + public List getVarintList(){ return null; } + public List getGroupList(){ return null; } + public boolean equals(Object p0){ return false; } + public int getSerializedSize(int p0){ return 0; } + public int getSerializedSizeAsMessageSetExtension(int p0){ return 0; } + public int hashCode(){ return 0; } + public static UnknownFieldSet.Field getDefaultInstance(){ return null; } + public static UnknownFieldSet.Field.Builder newBuilder(){ return null; } + public static UnknownFieldSet.Field.Builder newBuilder(UnknownFieldSet.Field p0){ return null; } + public void writeAsMessageSetExtensionTo(int p0, CodedOutputStream p1){} + public void writeTo(int p0, CodedOutputStream p1){} + static public class Builder + { + protected Builder() {} + public UnknownFieldSet.Field build(){ return null; } + public UnknownFieldSet.Field.Builder addFixed32(int p0){ return null; } + public UnknownFieldSet.Field.Builder addFixed64(long p0){ return null; } + public UnknownFieldSet.Field.Builder addGroup(UnknownFieldSet p0){ return null; } + public UnknownFieldSet.Field.Builder addLengthDelimited(ByteString p0){ return null; } + public UnknownFieldSet.Field.Builder addVarint(long p0){ return null; } + public UnknownFieldSet.Field.Builder clear(){ return null; } + public UnknownFieldSet.Field.Builder mergeFrom(UnknownFieldSet.Field p0){ return null; } + } + } + static public class Parser extends AbstractParser + { + public Parser(){} + public UnknownFieldSet parsePartialFrom(CodedInputStream p0, ExtensionRegistryLite p1){ return null; } + } +} diff --git a/java/ql/test/stubs/apache-hive/com/google/protobuf/WireFormat.java b/java/ql/test/stubs/apache-hive/com/google/protobuf/WireFormat.java new file mode 100644 index 00000000000..f14050a02f4 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/com/google/protobuf/WireFormat.java @@ -0,0 +1,29 @@ +// Generated automatically from com.google.protobuf.WireFormat for testing purposes + +package com.google.protobuf; + + +public class WireFormat +{ + protected WireFormat() {} + public static int WIRETYPE_END_GROUP = 0; + public static int WIRETYPE_FIXED32 = 0; + public static int WIRETYPE_FIXED64 = 0; + public static int WIRETYPE_LENGTH_DELIMITED = 0; + public static int WIRETYPE_START_GROUP = 0; + public static int WIRETYPE_VARINT = 0; + public static int getTagFieldNumber(int p0){ return 0; } + static public enum FieldType + { + BOOL, BYTES, DOUBLE, ENUM, FIXED32, FIXED64, FLOAT, GROUP, INT32, INT64, MESSAGE, SFIXED32, SFIXED64, SINT32, SINT64, STRING, UINT32, UINT64; + private FieldType() {} + public WireFormat.JavaType getJavaType(){ return null; } + public boolean isPackable(){ return false; } + public int getWireType(){ return 0; } + } + static public enum JavaType + { + BOOLEAN, BYTE_STRING, DOUBLE, ENUM, FLOAT, INT, LONG, MESSAGE, STRING; + private JavaType() {} + } +} diff --git a/java/ql/test/stubs/apache-hive/javax/crypto/SecretKey.java b/java/ql/test/stubs/apache-hive/javax/crypto/SecretKey.java new file mode 100644 index 00000000000..88c9e4539ba --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/crypto/SecretKey.java @@ -0,0 +1,11 @@ +// Generated automatically from javax.crypto.SecretKey for testing purposes + +package javax.crypto; + +import java.security.Key; +import javax.security.auth.Destroyable; + +public interface SecretKey extends Destroyable, Key +{ + static long serialVersionUID = 0; +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/AttributeConverter.java b/java/ql/test/stubs/apache-hive/javax/jdo/AttributeConverter.java new file mode 100644 index 00000000000..77795e95cb0 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/AttributeConverter.java @@ -0,0 +1,10 @@ +// Generated automatically from javax.jdo.AttributeConverter for testing purposes + +package javax.jdo; + + +public interface AttributeConverter +{ + A convertToAttribute(D p0); + D convertToDatastore(A p0); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/Extent.java b/java/ql/test/stubs/apache-hive/javax/jdo/Extent.java new file mode 100644 index 00000000000..f4c76947a9b --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/Extent.java @@ -0,0 +1,18 @@ +// Generated automatically from javax.jdo.Extent for testing purposes + +package javax.jdo; + +import java.util.Iterator; +import javax.jdo.FetchPlan; +import javax.jdo.PersistenceManager; + +public interface Extent extends java.lang.Iterable +{ + FetchPlan getFetchPlan(); + PersistenceManager getPersistenceManager(); + boolean hasSubclasses(); + java.lang.Class getCandidateClass(); + java.util.Iterator iterator(); + void close(java.util.Iterator p0); + void closeAll(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/FetchGroup.java b/java/ql/test/stubs/apache-hive/javax/jdo/FetchGroup.java new file mode 100644 index 00000000000..3ccab79965d --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/FetchGroup.java @@ -0,0 +1,31 @@ +// Generated automatically from javax.jdo.FetchGroup for testing purposes + +package javax.jdo; + +import java.util.Set; + +public interface FetchGroup +{ + Class getType(); + FetchGroup addCategory(String p0); + FetchGroup addMember(String p0); + FetchGroup addMembers(String... p0); + FetchGroup removeCategory(String p0); + FetchGroup removeMember(String p0); + FetchGroup removeMembers(String... p0); + FetchGroup setPostLoad(boolean p0); + FetchGroup setRecursionDepth(String p0, int p1); + FetchGroup setUnmodifiable(); + Set getMembers(); + String getName(); + boolean equals(Object p0); + boolean getPostLoad(); + boolean isUnmodifiable(); + int getRecursionDepth(String p0); + int hashCode(); + static String ALL = null; + static String BASIC = null; + static String DEFAULT = null; + static String MULTIVALUED = null; + static String RELATIONSHIP = null; +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/FetchPlan.java b/java/ql/test/stubs/apache-hive/javax/jdo/FetchPlan.java new file mode 100644 index 00000000000..35b684b85a9 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/FetchPlan.java @@ -0,0 +1,33 @@ +// Generated automatically from javax.jdo.FetchPlan for testing purposes + +package javax.jdo; + +import java.util.Collection; +import java.util.Set; + +public interface FetchPlan +{ + Class[] getDetachmentRootClasses(); + Collection getDetachmentRoots(); + FetchPlan addGroup(String p0); + FetchPlan clearGroups(); + FetchPlan removeGroup(String p0); + FetchPlan setDetachmentOptions(int p0); + FetchPlan setDetachmentRootClasses(Class... p0); + FetchPlan setDetachmentRoots(Collection p0); + FetchPlan setFetchSize(int p0); + FetchPlan setGroup(String p0); + FetchPlan setGroups(Collection p0); + FetchPlan setGroups(String... p0); + FetchPlan setMaxFetchDepth(int p0); + Set getGroups(); + int getDetachmentOptions(); + int getFetchSize(); + int getMaxFetchDepth(); + static String ALL = null; + static String DEFAULT = null; + static int DETACH_LOAD_FIELDS = 0; + static int DETACH_UNLOAD_FIELDS = 0; + static int FETCH_SIZE_GREEDY = 0; + static int FETCH_SIZE_OPTIMAL = 0; +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/JDOException.java b/java/ql/test/stubs/apache-hive/javax/jdo/JDOException.java new file mode 100644 index 00000000000..c2681bd8daa --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/JDOException.java @@ -0,0 +1,25 @@ +// Generated automatically from javax.jdo.JDOException for testing purposes + +package javax.jdo; + +import java.io.PrintStream; +import java.io.PrintWriter; + +public class JDOException extends RuntimeException +{ + public JDOException(){} + public JDOException(String p0){} + public JDOException(String p0, Object p1){} + public JDOException(String p0, Throwable p1){} + public JDOException(String p0, Throwable p1, Object p2){} + public JDOException(String p0, Throwable[] p1){} + public JDOException(String p0, Throwable[] p1, Object p2){} + public Object getFailedObject(){ return null; } + public String toString(){ return null; } + public Throwable getCause(){ return null; } + public Throwable initCause(Throwable p0){ return null; } + public Throwable[] getNestedExceptions(){ return null; } + public void printStackTrace(){} + public void printStackTrace(PrintStream p0){} + public void printStackTrace(PrintWriter p0){} +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/JDOQLTypedQuery.java b/java/ql/test/stubs/apache-hive/javax/jdo/JDOQLTypedQuery.java new file mode 100644 index 00000000000..98bc0285215 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/JDOQLTypedQuery.java @@ -0,0 +1,85 @@ +// Generated automatically from javax.jdo.JDOQLTypedQuery for testing purposes + +package javax.jdo; + +import java.io.Closeable; +import java.io.Serializable; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import javax.jdo.FetchPlan; +import javax.jdo.JDOQLTypedSubquery; +import javax.jdo.PersistenceManager; +import javax.jdo.query.BooleanExpression; +import javax.jdo.query.CharacterExpression; +import javax.jdo.query.CollectionExpression; +import javax.jdo.query.DateExpression; +import javax.jdo.query.DateTimeExpression; +import javax.jdo.query.Expression; +import javax.jdo.query.ListExpression; +import javax.jdo.query.MapExpression; +import javax.jdo.query.NumericExpression; +import javax.jdo.query.OrderExpression; +import javax.jdo.query.PersistableExpression; +import javax.jdo.query.StringExpression; +import javax.jdo.query.TimeExpression; + +public interface JDOQLTypedQuery extends Closeable, Serializable +{ +

    Expression

    parameter(String p0, Class

    p1); + R executeResultUnique(java.lang.Class p0); + java.util.List executeResultList(java.lang.Class p0); + JDOQLTypedSubquery subquery(java.lang.Class p0, String p1); + javax.jdo.query.Expression variable(String p0, java.lang.Class p1); + Boolean getSerializeRead(); + CharacterExpression characterParameter(String p0); + CollectionExpression collectionParameter(String p0); + DateExpression dateParameter(String p0); + DateTimeExpression datetimeParameter(String p0); + FetchPlan getFetchPlan(); + Integer getDatastoreReadTimeoutMillis(); + Integer getDatastoreWriteTimeoutMillis(); + JDOQLTypedQuery datastoreReadTimeoutMillis(Integer p0); + JDOQLTypedQuery datastoreWriteTimeoutMillis(Integer p0); + JDOQLTypedQuery excludeSubclasses(); + JDOQLTypedQuery extension(String p0, Object p1); + JDOQLTypedQuery extensions(Map p0); + JDOQLTypedQuery filter(BooleanExpression p0); + JDOQLTypedQuery groupBy(Expression... p0); + JDOQLTypedQuery having(Expression p0); + JDOQLTypedQuery ignoreCache(boolean p0); + JDOQLTypedQuery includeSubclasses(); + JDOQLTypedQuery orderBy(OrderExpression... p0); + JDOQLTypedQuery range(Expression p0, Expression p1); + JDOQLTypedQuery range(NumericExpression p0, NumericExpression p1); + JDOQLTypedQuery range(long p0, long p1); + JDOQLTypedQuery result(boolean p0, Expression... p1); + JDOQLTypedQuery saveAsNamedQuery(String p0); + JDOQLTypedQuery serializeRead(Boolean p0); + JDOQLTypedQuery setCandidates(java.util.Collection p0); + JDOQLTypedQuery setParameter(Expression p0, Object p1); + JDOQLTypedQuery setParameter(String p0, Object p1); + JDOQLTypedQuery setParameters(Map p0); + JDOQLTypedQuery unmodifiable(); + JDOQLTypedSubquery subquery(String p0); + List executeResultList(); + ListExpression listParameter(String p0); + MapExpression mapParameter(String p0); + NumericExpression numericParameter(String p0); + Object executeResultUnique(); + PersistenceManager getPersistenceManager(); + String toString(); + StringExpression stringParameter(String p0); + T executeUnique(); + TimeExpression timeParameter(String p0); + boolean getIgnoreCache(); + boolean isUnmodifiable(); + java.util.List executeList(); + javax.jdo.query.PersistableExpression candidate(); + long deletePersistentAll(); + static String QUERY_CLASS_PREFIX = null; + void cancel(Thread p0); + void cancelAll(); + void close(Object p0); + void closeAll(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/JDOQLTypedSubquery.java b/java/ql/test/stubs/apache-hive/javax/jdo/JDOQLTypedSubquery.java new file mode 100644 index 00000000000..ba614e4a748 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/JDOQLTypedSubquery.java @@ -0,0 +1,31 @@ +// Generated automatically from javax.jdo.JDOQLTypedSubquery for testing purposes + +package javax.jdo; + +import java.io.Serializable; +import java.util.Collection; +import javax.jdo.query.BooleanExpression; +import javax.jdo.query.CharacterExpression; +import javax.jdo.query.CollectionExpression; +import javax.jdo.query.DateExpression; +import javax.jdo.query.DateTimeExpression; +import javax.jdo.query.Expression; +import javax.jdo.query.NumericExpression; +import javax.jdo.query.PersistableExpression; +import javax.jdo.query.StringExpression; +import javax.jdo.query.TimeExpression; + +public interface JDOQLTypedSubquery extends Serializable +{ + NumericExpression selectUnique(NumericExpression p0); + CharacterExpression selectUnique(CharacterExpression p0); + CollectionExpression select(CollectionExpression p0); + DateExpression selectUnique(DateExpression p0); + DateTimeExpression selectUnique(DateTimeExpression p0); + JDOQLTypedSubquery filter(BooleanExpression p0); + JDOQLTypedSubquery groupBy(Expression... p0); + JDOQLTypedSubquery having(Expression p0); + StringExpression selectUnique(StringExpression p0); + TimeExpression selectUnique(TimeExpression p0); + javax.jdo.query.PersistableExpression candidate(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/ObjectState.java b/java/ql/test/stubs/apache-hive/javax/jdo/ObjectState.java new file mode 100644 index 00000000000..87b9aaf61ec --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/ObjectState.java @@ -0,0 +1,11 @@ +// Generated automatically from javax.jdo.ObjectState for testing purposes + +package javax.jdo; + + +public enum ObjectState +{ + DETACHED_CLEAN, DETACHED_DIRTY, HOLLOW_PERSISTENT_NONTRANSACTIONAL, PERSISTENT_CLEAN, PERSISTENT_DELETED, PERSISTENT_DIRTY, PERSISTENT_NEW, PERSISTENT_NEW_DELETED, PERSISTENT_NONTRANSACTIONAL_DIRTY, TRANSIENT, TRANSIENT_CLEAN, TRANSIENT_DIRTY; + private ObjectState() {} + public String toString(){ return null; } +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/PersistenceManager.java b/java/ql/test/stubs/apache-hive/javax/jdo/PersistenceManager.java new file mode 100644 index 00000000000..f9d8c6b7dd6 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/PersistenceManager.java @@ -0,0 +1,125 @@ +// Generated automatically from javax.jdo.PersistenceManager for testing purposes + +package javax.jdo; + +import java.util.Collection; +import java.util.Date; +import java.util.EnumSet; +import java.util.Map; +import java.util.Set; +import javax.jdo.Extent; +import javax.jdo.FetchGroup; +import javax.jdo.FetchPlan; +import javax.jdo.JDOException; +import javax.jdo.JDOQLTypedQuery; +import javax.jdo.ObjectState; +import javax.jdo.PersistenceManagerFactory; +import javax.jdo.Query; +import javax.jdo.Transaction; +import javax.jdo.datastore.JDOConnection; +import javax.jdo.datastore.Sequence; +import javax.jdo.listener.InstanceLifecycleListener; + +public interface PersistenceManager extends AutoCloseable +{ + JDOQLTypedQuery newJDOQLTypedQuery(java.lang.Class p0); + T detachCopy(T p0); + T getObjectById(java.lang.Class p0, Object p1); + T makePersistent(T p0); + T newInstance(java.lang.Class p0); + T[] detachCopyAll(T... p0); + T[] makePersistentAll(T... p0); + java.util.Collection detachCopyAll(java.util.Collection p0); + java.util.Collection makePersistentAll(java.util.Collection p0); + javax.jdo.Extent getExtent(java.lang.Class p0); + javax.jdo.Extent getExtent(java.lang.Class p0, boolean p1); + javax.jdo.Query newNamedQuery(java.lang.Class p0, String p1); + javax.jdo.Query newQuery(java.lang.Class p0); + javax.jdo.Query newQuery(java.lang.Class p0, String p1); + javax.jdo.Query newQuery(java.lang.Class p0, java.util.Collection p1); + javax.jdo.Query newQuery(java.lang.Class p0, java.util.Collection p1, String p2); + javax.jdo.Query newQuery(javax.jdo.Extent p0); + javax.jdo.Query newQuery(javax.jdo.Extent p0, String p1); + Class getObjectIdClass(Class p0); + Collection getObjectsById(Collection p0); + Collection getObjectsById(Collection p0, boolean p1); + Date getServerDate(); + FetchGroup getFetchGroup(Class p0, String p1); + FetchPlan getFetchPlan(); + Integer getDatastoreReadTimeoutMillis(); + Integer getDatastoreWriteTimeoutMillis(); + JDOConnection getDataStoreConnection(); + Map getProperties(); + Object getObjectById(Object p0); + Object getObjectById(Object p0, boolean p1); + Object getObjectId(Object p0); + Object getTransactionalObjectId(Object p0); + Object getUserObject(); + Object getUserObject(Object p0); + Object newObjectIdInstance(Class p0, Object p1); + Object putUserObject(Object p0, Object p1); + Object removeUserObject(Object p0); + Object[] getObjectsById(Object... p0); + Object[] getObjectsById(boolean p0, Object... p1); + PersistenceManagerFactory getPersistenceManagerFactory(); + Query newQuery(); + Query newQuery(Object p0); + Query newQuery(String p0); + Query newQuery(String p0, Object p1); + Sequence getSequence(String p0); + Set getManagedObjects(); + Set getManagedObjects(Class... p0); + Set getManagedObjects(EnumSet p0); + Set getManagedObjects(EnumSet p0, Class... p1); + Set getSupportedProperties(); + Transaction currentTransaction(); + boolean getCopyOnAttach(); + boolean getDetachAllOnCommit(); + boolean getIgnoreCache(); + boolean getMultithreaded(); + boolean isClosed(); + void addInstanceLifecycleListener(InstanceLifecycleListener p0, Class... p1); + void checkConsistency(); + void close(); + void deletePersistent(Object p0); + void deletePersistentAll(Collection p0); + void deletePersistentAll(Object... p0); + void evict(Object p0); + void evictAll(); + void evictAll(Collection p0); + void evictAll(Object... p0); + void evictAll(boolean p0, Class p1); + void flush(); + void makeNontransactional(Object p0); + void makeNontransactionalAll(Collection p0); + void makeNontransactionalAll(Object... p0); + void makeTransactional(Object p0); + void makeTransactionalAll(Collection p0); + void makeTransactionalAll(Object... p0); + void makeTransient(Object p0); + void makeTransient(Object p0, boolean p1); + void makeTransientAll(Collection p0); + void makeTransientAll(Collection p0, boolean p1); + void makeTransientAll(Object... p0); + void makeTransientAll(boolean p0, Object... p1); + void refresh(Object p0); + void refreshAll(); + void refreshAll(Collection p0); + void refreshAll(JDOException p0); + void refreshAll(Object... p0); + void removeInstanceLifecycleListener(InstanceLifecycleListener p0); + void retrieve(Object p0); + void retrieve(Object p0, boolean p1); + void retrieveAll(Collection p0); + void retrieveAll(Collection p0, boolean p1); + void retrieveAll(Object... p0); + void retrieveAll(boolean p0, Object... p1); + void setCopyOnAttach(boolean p0); + void setDatastoreReadTimeoutMillis(Integer p0); + void setDatastoreWriteTimeoutMillis(Integer p0); + void setDetachAllOnCommit(boolean p0); + void setIgnoreCache(boolean p0); + void setMultithreaded(boolean p0); + void setProperty(String p0, Object p1); + void setUserObject(Object p0); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/PersistenceManagerFactory.java b/java/ql/test/stubs/apache-hive/javax/jdo/PersistenceManagerFactory.java new file mode 100644 index 00000000000..5fe1a7096cc --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/PersistenceManagerFactory.java @@ -0,0 +1,88 @@ +// Generated automatically from javax.jdo.PersistenceManagerFactory for testing purposes + +package javax.jdo; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Properties; +import java.util.Set; +import javax.jdo.FetchGroup; +import javax.jdo.PersistenceManager; +import javax.jdo.datastore.DataStoreCache; +import javax.jdo.listener.InstanceLifecycleListener; +import javax.jdo.metadata.JDOMetadata; +import javax.jdo.metadata.TypeMetadata; + +public interface PersistenceManagerFactory extends Serializable +{ + Collection getManagedClasses(); + Collection supportedOptions(); + DataStoreCache getDataStoreCache(); + FetchGroup getFetchGroup(Class p0, String p1); + Integer getDatastoreReadTimeoutMillis(); + Integer getDatastoreWriteTimeoutMillis(); + JDOMetadata newMetadata(); + Object getConnectionFactory(); + Object getConnectionFactory2(); + PersistenceManager getPersistenceManager(); + PersistenceManager getPersistenceManager(String p0, String p1); + PersistenceManager getPersistenceManagerProxy(); + Properties getProperties(); + Set getFetchGroups(); + String getConnectionDriverName(); + String getConnectionFactory2Name(); + String getConnectionFactoryName(); + String getConnectionURL(); + String getConnectionUserName(); + String getMapping(); + String getName(); + String getPersistenceUnitName(); + String getServerTimeZoneID(); + String getTransactionIsolationLevel(); + String getTransactionType(); + TypeMetadata getMetadata(String p0); + boolean getCopyOnAttach(); + boolean getDetachAllOnCommit(); + boolean getIgnoreCache(); + boolean getMultithreaded(); + boolean getNontransactionalRead(); + boolean getNontransactionalWrite(); + boolean getOptimistic(); + boolean getReadOnly(); + boolean getRestoreValues(); + boolean getRetainValues(); + boolean isClosed(); + void addFetchGroups(FetchGroup... p0); + void addInstanceLifecycleListener(InstanceLifecycleListener p0, Class[] p1); + void close(); + void registerMetadata(JDOMetadata p0); + void removeAllFetchGroups(); + void removeFetchGroups(FetchGroup... p0); + void removeInstanceLifecycleListener(InstanceLifecycleListener p0); + void setConnectionDriverName(String p0); + void setConnectionFactory(Object p0); + void setConnectionFactory2(Object p0); + void setConnectionFactory2Name(String p0); + void setConnectionFactoryName(String p0); + void setConnectionPassword(String p0); + void setConnectionURL(String p0); + void setConnectionUserName(String p0); + void setCopyOnAttach(boolean p0); + void setDatastoreReadTimeoutMillis(Integer p0); + void setDatastoreWriteTimeoutMillis(Integer p0); + void setDetachAllOnCommit(boolean p0); + void setIgnoreCache(boolean p0); + void setMapping(String p0); + void setMultithreaded(boolean p0); + void setName(String p0); + void setNontransactionalRead(boolean p0); + void setNontransactionalWrite(boolean p0); + void setOptimistic(boolean p0); + void setPersistenceUnitName(String p0); + void setReadOnly(boolean p0); + void setRestoreValues(boolean p0); + void setRetainValues(boolean p0); + void setServerTimeZoneID(String p0); + void setTransactionIsolationLevel(String p0); + void setTransactionType(String p0); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/Query.java b/java/ql/test/stubs/apache-hive/javax/jdo/Query.java new file mode 100644 index 00000000000..4ba4c503d71 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/Query.java @@ -0,0 +1,92 @@ +// Generated automatically from javax.jdo.Query for testing purposes + +package javax.jdo; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import javax.jdo.Extent; +import javax.jdo.FetchPlan; +import javax.jdo.PersistenceManager; + +public interface Query extends AutoCloseable, Serializable +{ + R executeResultUnique(java.lang.Class p0); + java.util.List executeResultList(java.lang.Class p0); + Boolean getSerializeRead(); + FetchPlan getFetchPlan(); + Integer getDatastoreReadTimeoutMillis(); + Integer getDatastoreWriteTimeoutMillis(); + List executeResultList(); + Object execute(); + Object execute(Object p0); + Object execute(Object p0, Object p1); + Object execute(Object p0, Object p1, Object p2); + Object executeResultUnique(); + Object executeWithArray(Object... p0); + Object executeWithMap(Map p0); + PersistenceManager getPersistenceManager(); + Query datastoreReadTimeoutMillis(Integer p0); + Query datastoreWriteTimeoutMillis(Integer p0); + Query extension(String p0, Object p1); + Query extensions(Map p0); + Query filter(String p0); + Query groupBy(String p0); + Query ignoreCache(boolean p0); + Query imports(String p0); + Query orderBy(String p0); + Query parameters(String p0); + Query range(String p0); + Query range(long p0, long p1); + Query result(String p0); + Query saveAsNamedQuery(String p0); + Query serializeRead(Boolean p0); + Query setNamedParameters(Map p0); + Query setParameters(Object... p0); + Query subquery(Query p0, String p1, String p2); + Query subquery(Query p0, String p1, String p2, Map p3); + Query subquery(Query p0, String p1, String p2, String p3); + Query subquery(Query p0, String p1, String p2, String... p3); + Query unmodifiable(); + Query variables(String p0); + T executeUnique(); + boolean getIgnoreCache(); + boolean isUnmodifiable(); + java.util.List executeList(); + long deletePersistentAll(); + long deletePersistentAll(Map p0); + long deletePersistentAll(Object... p0); + static String JDOQL = null; + static String SQL = null; + void addExtension(String p0, Object p1); + void addSubquery(Query p0, String p1, String p2); + void addSubquery(Query p0, String p1, String p2, Map p3); + void addSubquery(Query p0, String p1, String p2, String p3); + void addSubquery(Query p0, String p1, String p2, String... p3); + void cancel(Thread p0); + void cancelAll(); + void close(Object p0); + void closeAll(); + void compile(); + void declareImports(String p0); + void declareParameters(String p0); + void declareVariables(String p0); + void setCandidates(java.util.Collection p0); + void setCandidates(javax.jdo.Extent p0); + void setClass(java.lang.Class p0); + void setDatastoreReadTimeoutMillis(Integer p0); + void setDatastoreWriteTimeoutMillis(Integer p0); + void setExtensions(Map p0); + void setFilter(String p0); + void setGrouping(String p0); + void setIgnoreCache(boolean p0); + void setOrdering(String p0); + void setRange(String p0); + void setRange(long p0, long p1); + void setResult(String p0); + void setResultClass(Class p0); + void setSerializeRead(Boolean p0); + void setUnique(boolean p0); + void setUnmodifiable(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/Transaction.java b/java/ql/test/stubs/apache-hive/javax/jdo/Transaction.java new file mode 100644 index 00000000000..139ff79df16 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/Transaction.java @@ -0,0 +1,33 @@ +// Generated automatically from javax.jdo.Transaction for testing purposes + +package javax.jdo; + +import javax.jdo.PersistenceManager; +import javax.transaction.Synchronization; + +public interface Transaction +{ + Boolean getSerializeRead(); + PersistenceManager getPersistenceManager(); + String getIsolationLevel(); + Synchronization getSynchronization(); + boolean getNontransactionalRead(); + boolean getNontransactionalWrite(); + boolean getOptimistic(); + boolean getRestoreValues(); + boolean getRetainValues(); + boolean getRollbackOnly(); + boolean isActive(); + void begin(); + void commit(); + void rollback(); + void setIsolationLevel(String p0); + void setNontransactionalRead(boolean p0); + void setNontransactionalWrite(boolean p0); + void setOptimistic(boolean p0); + void setRestoreValues(boolean p0); + void setRetainValues(boolean p0); + void setRollbackOnly(); + void setSerializeRead(Boolean p0); + void setSynchronization(Synchronization p0); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/annotations/DiscriminatorStrategy.java b/java/ql/test/stubs/apache-hive/javax/jdo/annotations/DiscriminatorStrategy.java new file mode 100644 index 00000000000..96a2a3a2403 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/annotations/DiscriminatorStrategy.java @@ -0,0 +1,10 @@ +// Generated automatically from javax.jdo.annotations.DiscriminatorStrategy for testing purposes + +package javax.jdo.annotations; + + +public enum DiscriminatorStrategy +{ + CLASS_NAME, NONE, UNSPECIFIED, VALUE_MAP; + private DiscriminatorStrategy() {} +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/annotations/ForeignKeyAction.java b/java/ql/test/stubs/apache-hive/javax/jdo/annotations/ForeignKeyAction.java new file mode 100644 index 00000000000..eeba599f2da --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/annotations/ForeignKeyAction.java @@ -0,0 +1,10 @@ +// Generated automatically from javax.jdo.annotations.ForeignKeyAction for testing purposes + +package javax.jdo.annotations; + + +public enum ForeignKeyAction +{ + CASCADE, DEFAULT, NONE, NULL, RESTRICT, UNSPECIFIED; + private ForeignKeyAction() {} +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/annotations/IdGeneratorStrategy.java b/java/ql/test/stubs/apache-hive/javax/jdo/annotations/IdGeneratorStrategy.java new file mode 100644 index 00000000000..493398c9d63 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/annotations/IdGeneratorStrategy.java @@ -0,0 +1,10 @@ +// Generated automatically from javax.jdo.annotations.IdGeneratorStrategy for testing purposes + +package javax.jdo.annotations; + + +public enum IdGeneratorStrategy +{ + IDENTITY, INCREMENT, NATIVE, SEQUENCE, UNSPECIFIED, UUIDHEX, UUIDSTRING; + private IdGeneratorStrategy() {} +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/annotations/IdentityType.java b/java/ql/test/stubs/apache-hive/javax/jdo/annotations/IdentityType.java new file mode 100644 index 00000000000..60902f78a1d --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/annotations/IdentityType.java @@ -0,0 +1,10 @@ +// Generated automatically from javax.jdo.annotations.IdentityType for testing purposes + +package javax.jdo.annotations; + + +public enum IdentityType +{ + APPLICATION, DATASTORE, NONDURABLE, UNSPECIFIED; + private IdentityType() {} +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/annotations/InheritanceStrategy.java b/java/ql/test/stubs/apache-hive/javax/jdo/annotations/InheritanceStrategy.java new file mode 100644 index 00000000000..d600a3c0f6a --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/annotations/InheritanceStrategy.java @@ -0,0 +1,10 @@ +// Generated automatically from javax.jdo.annotations.InheritanceStrategy for testing purposes + +package javax.jdo.annotations; + + +public enum InheritanceStrategy +{ + COMPLETE_TABLE, NEW_TABLE, SUBCLASS_TABLE, SUPERCLASS_TABLE, UNSPECIFIED; + private InheritanceStrategy() {} +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/annotations/NullValue.java b/java/ql/test/stubs/apache-hive/javax/jdo/annotations/NullValue.java new file mode 100644 index 00000000000..19840d45147 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/annotations/NullValue.java @@ -0,0 +1,10 @@ +// Generated automatically from javax.jdo.annotations.NullValue for testing purposes + +package javax.jdo.annotations; + + +public enum NullValue +{ + DEFAULT, EXCEPTION, NONE; + private NullValue() {} +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/annotations/PersistenceModifier.java b/java/ql/test/stubs/apache-hive/javax/jdo/annotations/PersistenceModifier.java new file mode 100644 index 00000000000..a2a5f684b5b --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/annotations/PersistenceModifier.java @@ -0,0 +1,10 @@ +// Generated automatically from javax.jdo.annotations.PersistenceModifier for testing purposes + +package javax.jdo.annotations; + + +public enum PersistenceModifier +{ + NONE, PERSISTENT, TRANSACTIONAL, UNSPECIFIED; + private PersistenceModifier() {} +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/annotations/SequenceStrategy.java b/java/ql/test/stubs/apache-hive/javax/jdo/annotations/SequenceStrategy.java new file mode 100644 index 00000000000..f392ed59310 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/annotations/SequenceStrategy.java @@ -0,0 +1,10 @@ +// Generated automatically from javax.jdo.annotations.SequenceStrategy for testing purposes + +package javax.jdo.annotations; + + +public enum SequenceStrategy +{ + CONTIGUOUS, NONCONTIGUOUS, NONTRANSACTIONAL; + private SequenceStrategy() {} +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/annotations/VersionStrategy.java b/java/ql/test/stubs/apache-hive/javax/jdo/annotations/VersionStrategy.java new file mode 100644 index 00000000000..70fc0908625 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/annotations/VersionStrategy.java @@ -0,0 +1,10 @@ +// Generated automatically from javax.jdo.annotations.VersionStrategy for testing purposes + +package javax.jdo.annotations; + + +public enum VersionStrategy +{ + DATE_TIME, NONE, STATE_IMAGE, UNSPECIFIED, VERSION_NUMBER; + private VersionStrategy() {} +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/datastore/DataStoreCache.java b/java/ql/test/stubs/apache-hive/javax/jdo/datastore/DataStoreCache.java new file mode 100644 index 00000000000..ddaf326fbd0 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/datastore/DataStoreCache.java @@ -0,0 +1,22 @@ +// Generated automatically from javax.jdo.datastore.DataStoreCache for testing purposes + +package javax.jdo.datastore; + +import java.util.Collection; + +public interface DataStoreCache +{ + void evict(Object p0); + void evictAll(); + void evictAll(Collection p0); + void evictAll(Object... p0); + void evictAll(boolean p0, Class p1); + void pin(Object p0); + void pinAll(Collection p0); + void pinAll(Object... p0); + void pinAll(boolean p0, Class p1); + void unpin(Object p0); + void unpinAll(Collection p0); + void unpinAll(Object... p0); + void unpinAll(boolean p0, Class p1); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/datastore/JDOConnection.java b/java/ql/test/stubs/apache-hive/javax/jdo/datastore/JDOConnection.java new file mode 100644 index 00000000000..de8e896f56f --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/datastore/JDOConnection.java @@ -0,0 +1,10 @@ +// Generated automatically from javax.jdo.datastore.JDOConnection for testing purposes + +package javax.jdo.datastore; + + +public interface JDOConnection +{ + Object getNativeConnection(); + void close(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/datastore/Sequence.java b/java/ql/test/stubs/apache-hive/javax/jdo/datastore/Sequence.java new file mode 100644 index 00000000000..2a8494fe3bd --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/datastore/Sequence.java @@ -0,0 +1,14 @@ +// Generated automatically from javax.jdo.datastore.Sequence for testing purposes + +package javax.jdo.datastore; + + +public interface Sequence +{ + Object current(); + Object next(); + String getName(); + long currentValue(); + long nextValue(); + void allocate(int p0); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/listener/InstanceLifecycleListener.java b/java/ql/test/stubs/apache-hive/javax/jdo/listener/InstanceLifecycleListener.java new file mode 100644 index 00000000000..68db5cbac68 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/listener/InstanceLifecycleListener.java @@ -0,0 +1,8 @@ +// Generated automatically from javax.jdo.listener.InstanceLifecycleListener for testing purposes + +package javax.jdo.listener; + + +public interface InstanceLifecycleListener +{ +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/ArrayMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/ArrayMetadata.java new file mode 100644 index 00000000000..1b4149d5980 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/ArrayMetadata.java @@ -0,0 +1,17 @@ +// Generated automatically from javax.jdo.metadata.ArrayMetadata for testing purposes + +package javax.jdo.metadata; + +import javax.jdo.metadata.Metadata; + +public interface ArrayMetadata extends Metadata +{ + ArrayMetadata setDependentElement(boolean p0); + ArrayMetadata setElementType(String p0); + ArrayMetadata setEmbeddedElement(boolean p0); + ArrayMetadata setSerializedElement(boolean p0); + Boolean getDependentElement(); + Boolean getEmbeddedElement(); + Boolean getSerializedElement(); + String getElementType(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/ClassMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/ClassMetadata.java new file mode 100644 index 00000000000..466ca5a633b --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/ClassMetadata.java @@ -0,0 +1,16 @@ +// Generated automatically from javax.jdo.metadata.ClassMetadata for testing purposes + +package javax.jdo.metadata; + +import java.lang.reflect.Field; +import javax.jdo.metadata.ClassPersistenceModifier; +import javax.jdo.metadata.FieldMetadata; +import javax.jdo.metadata.TypeMetadata; + +public interface ClassMetadata extends TypeMetadata +{ + ClassMetadata setPersistenceModifier(ClassPersistenceModifier p0); + ClassPersistenceModifier getPersistenceModifier(); + FieldMetadata newFieldMetadata(Field p0); + FieldMetadata newFieldMetadata(String p0); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/ClassPersistenceModifier.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/ClassPersistenceModifier.java new file mode 100644 index 00000000000..054ff77d9a6 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/ClassPersistenceModifier.java @@ -0,0 +1,10 @@ +// Generated automatically from javax.jdo.metadata.ClassPersistenceModifier for testing purposes + +package javax.jdo.metadata; + + +public enum ClassPersistenceModifier +{ + NON_PERSISTENT, PERSISTENCE_AWARE, PERSISTENCE_CAPABLE; + private ClassPersistenceModifier() {} +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/CollectionMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/CollectionMetadata.java new file mode 100644 index 00000000000..098d264df54 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/CollectionMetadata.java @@ -0,0 +1,17 @@ +// Generated automatically from javax.jdo.metadata.CollectionMetadata for testing purposes + +package javax.jdo.metadata; + +import javax.jdo.metadata.Metadata; + +public interface CollectionMetadata extends Metadata +{ + Boolean getDependentElement(); + Boolean getEmbeddedElement(); + Boolean getSerializedElement(); + CollectionMetadata setDependentElement(boolean p0); + CollectionMetadata setElementType(String p0); + CollectionMetadata setEmbeddedElement(boolean p0); + CollectionMetadata setSerializedElement(boolean p0); + String getElementType(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/ColumnMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/ColumnMetadata.java new file mode 100644 index 00000000000..31684699a00 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/ColumnMetadata.java @@ -0,0 +1,31 @@ +// Generated automatically from javax.jdo.metadata.ColumnMetadata for testing purposes + +package javax.jdo.metadata; + +import javax.jdo.metadata.Metadata; + +public interface ColumnMetadata extends Metadata +{ + Boolean getAllowsNull(); + ColumnMetadata setAllowsNull(boolean p0); + ColumnMetadata setDefaultValue(String p0); + ColumnMetadata setInsertValue(String p0); + ColumnMetadata setJDBCType(String p0); + ColumnMetadata setLength(int p0); + ColumnMetadata setName(String p0); + ColumnMetadata setPosition(int p0); + ColumnMetadata setSQLType(String p0); + ColumnMetadata setScale(int p0); + ColumnMetadata setTarget(String p0); + ColumnMetadata setTargetField(String p0); + Integer getLength(); + Integer getPosition(); + Integer getScale(); + String getDefaultValue(); + String getInsertValue(); + String getJDBCType(); + String getName(); + String getSQLType(); + String getTarget(); + String getTargetField(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/DatastoreIdentityMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/DatastoreIdentityMetadata.java new file mode 100644 index 00000000000..e5f53ef9ec7 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/DatastoreIdentityMetadata.java @@ -0,0 +1,22 @@ +// Generated automatically from javax.jdo.metadata.DatastoreIdentityMetadata for testing purposes + +package javax.jdo.metadata; + +import javax.jdo.annotations.IdGeneratorStrategy; +import javax.jdo.metadata.ColumnMetadata; +import javax.jdo.metadata.Metadata; + +public interface DatastoreIdentityMetadata extends Metadata +{ + ColumnMetadata newColumnMetadata(); + ColumnMetadata[] getColumns(); + DatastoreIdentityMetadata setColumn(String p0); + DatastoreIdentityMetadata setCustomStrategy(String p0); + DatastoreIdentityMetadata setSequence(String p0); + DatastoreIdentityMetadata setStrategy(IdGeneratorStrategy p0); + IdGeneratorStrategy getStrategy(); + String getColumn(); + String getCustomStrategy(); + String getSequence(); + int getNumberOfColumns(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/DiscriminatorMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/DiscriminatorMetadata.java new file mode 100644 index 00000000000..48291a4c92a --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/DiscriminatorMetadata.java @@ -0,0 +1,26 @@ +// Generated automatically from javax.jdo.metadata.DiscriminatorMetadata for testing purposes + +package javax.jdo.metadata; + +import javax.jdo.annotations.DiscriminatorStrategy; +import javax.jdo.metadata.ColumnMetadata; +import javax.jdo.metadata.IndexMetadata; +import javax.jdo.metadata.Indexed; +import javax.jdo.metadata.Metadata; + +public interface DiscriminatorMetadata extends Metadata +{ + ColumnMetadata newColumnMetadata(); + ColumnMetadata[] getColumns(); + DiscriminatorMetadata setColumn(String p0); + DiscriminatorMetadata setIndexed(Indexed p0); + DiscriminatorMetadata setStrategy(DiscriminatorStrategy p0); + DiscriminatorMetadata setValue(String p0); + DiscriminatorStrategy getStrategy(); + IndexMetadata getIndexMetadata(); + IndexMetadata newIndexMetadata(); + Indexed getIndexed(); + String getColumn(); + String getValue(); + int getNumberOfColumns(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/ElementMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/ElementMetadata.java new file mode 100644 index 00000000000..f30c9b965d7 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/ElementMetadata.java @@ -0,0 +1,39 @@ +// Generated automatically from javax.jdo.metadata.ElementMetadata for testing purposes + +package javax.jdo.metadata; + +import javax.jdo.AttributeConverter; +import javax.jdo.annotations.ForeignKeyAction; +import javax.jdo.metadata.ColumnMetadata; +import javax.jdo.metadata.EmbeddedMetadata; +import javax.jdo.metadata.ForeignKeyMetadata; +import javax.jdo.metadata.IndexMetadata; +import javax.jdo.metadata.Metadata; +import javax.jdo.metadata.UniqueMetadata; + +public interface ElementMetadata extends Metadata +{ + AttributeConverter getConverter(); + Boolean getUseDefaultConversion(); + ColumnMetadata newColumnMetadata(); + ColumnMetadata[] getColumns(); + ElementMetadata setColumn(String p0); + ElementMetadata setConverter(AttributeConverter p0); + ElementMetadata setDeleteAction(ForeignKeyAction p0); + ElementMetadata setTable(String p0); + ElementMetadata setUpdateAction(ForeignKeyAction p0); + ElementMetadata setUseDefaultConversion(Boolean p0); + EmbeddedMetadata getEmbeddedMetadata(); + EmbeddedMetadata newEmbeddedMetadata(); + ForeignKeyAction getDeleteAction(); + ForeignKeyAction getUpdateAction(); + ForeignKeyMetadata getForeignKeyMetadata(); + ForeignKeyMetadata newForeignKeyMetadata(); + IndexMetadata getIndexMetadata(); + IndexMetadata newIndexMetadata(); + String getColumn(); + String getTable(); + UniqueMetadata getUniqueMetadata(); + UniqueMetadata newUniqueMetadata(); + int getNumberOfColumns(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/EmbeddedMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/EmbeddedMetadata.java new file mode 100644 index 00000000000..782d27ca7fc --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/EmbeddedMetadata.java @@ -0,0 +1,25 @@ +// Generated automatically from javax.jdo.metadata.EmbeddedMetadata for testing purposes + +package javax.jdo.metadata; + +import javax.jdo.metadata.DiscriminatorMetadata; +import javax.jdo.metadata.FieldMetadata; +import javax.jdo.metadata.MemberMetadata; +import javax.jdo.metadata.Metadata; +import javax.jdo.metadata.PropertyMetadata; + +public interface EmbeddedMetadata extends Metadata +{ + DiscriminatorMetadata getDiscriminatorMetadata(); + DiscriminatorMetadata newDiscriminatorMetadata(); + EmbeddedMetadata setNullIndicatorColumn(String p0); + EmbeddedMetadata setNullIndicatorValue(String p0); + EmbeddedMetadata setOwnerMember(String p0); + FieldMetadata newFieldMetadata(String p0); + MemberMetadata[] getMembers(); + PropertyMetadata newPropertyMetadata(String p0); + String getNullIndicatorColumn(); + String getNullIndicatorValue(); + String getOwnerMember(); + int getNumberOfMembers(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/ExtensionMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/ExtensionMetadata.java new file mode 100644 index 00000000000..7229487911f --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/ExtensionMetadata.java @@ -0,0 +1,11 @@ +// Generated automatically from javax.jdo.metadata.ExtensionMetadata for testing purposes + +package javax.jdo.metadata; + + +public interface ExtensionMetadata +{ + String getKey(); + String getValue(); + String getVendorName(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/FetchGroupMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/FetchGroupMetadata.java new file mode 100644 index 00000000000..3ce3a6b9229 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/FetchGroupMetadata.java @@ -0,0 +1,19 @@ +// Generated automatically from javax.jdo.metadata.FetchGroupMetadata for testing purposes + +package javax.jdo.metadata; + +import javax.jdo.metadata.FieldMetadata; +import javax.jdo.metadata.MemberMetadata; +import javax.jdo.metadata.Metadata; +import javax.jdo.metadata.PropertyMetadata; + +public interface FetchGroupMetadata extends Metadata +{ + Boolean getPostLoad(); + FetchGroupMetadata setPostLoad(boolean p0); + FieldMetadata newFieldMetadata(String p0); + MemberMetadata[] getMembers(); + PropertyMetadata newPropertyMetadata(String p0); + String getName(); + int getNumberOfMembers(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/FetchPlanMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/FetchPlanMetadata.java new file mode 100644 index 00000000000..c78c53030d3 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/FetchPlanMetadata.java @@ -0,0 +1,18 @@ +// Generated automatically from javax.jdo.metadata.FetchPlanMetadata for testing purposes + +package javax.jdo.metadata; + +import javax.jdo.metadata.FetchGroupMetadata; +import javax.jdo.metadata.Metadata; + +public interface FetchPlanMetadata extends Metadata +{ + FetchGroupMetadata newFetchGroupMetadata(String p0); + FetchGroupMetadata[] getFetchGroups(); + FetchPlanMetadata setFetchSize(int p0); + FetchPlanMetadata setMaxFetchDepth(int p0); + String getName(); + int getFetchSize(); + int getMaxFetchDepth(); + int getNumberOfFetchGroups(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/FieldMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/FieldMetadata.java new file mode 100644 index 00000000000..bd503d4c9fe --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/FieldMetadata.java @@ -0,0 +1,9 @@ +// Generated automatically from javax.jdo.metadata.FieldMetadata for testing purposes + +package javax.jdo.metadata; + +import javax.jdo.metadata.MemberMetadata; + +public interface FieldMetadata extends MemberMetadata +{ +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/ForeignKeyMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/ForeignKeyMetadata.java new file mode 100644 index 00000000000..d88fa5a6ee9 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/ForeignKeyMetadata.java @@ -0,0 +1,33 @@ +// Generated automatically from javax.jdo.metadata.ForeignKeyMetadata for testing purposes + +package javax.jdo.metadata; + +import javax.jdo.annotations.ForeignKeyAction; +import javax.jdo.metadata.ColumnMetadata; +import javax.jdo.metadata.FieldMetadata; +import javax.jdo.metadata.MemberMetadata; +import javax.jdo.metadata.Metadata; +import javax.jdo.metadata.PropertyMetadata; + +public interface ForeignKeyMetadata extends Metadata +{ + Boolean getDeferred(); + Boolean getUnique(); + ColumnMetadata newColumnMetadata(); + ColumnMetadata[] getColumns(); + FieldMetadata newFieldMetadata(String p0); + ForeignKeyAction getDeleteAction(); + ForeignKeyAction getUpdateAction(); + ForeignKeyMetadata setDeferred(boolean p0); + ForeignKeyMetadata setDeleteAction(ForeignKeyAction p0); + ForeignKeyMetadata setName(String p0); + ForeignKeyMetadata setTable(String p0); + ForeignKeyMetadata setUnique(boolean p0); + ForeignKeyMetadata setUpdateAction(ForeignKeyAction p0); + MemberMetadata[] getMembers(); + PropertyMetadata newPropertyMetadata(String p0); + String getName(); + String getTable(); + int getNumberOfColumns(); + int getNumberOfMembers(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/IndexMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/IndexMetadata.java new file mode 100644 index 00000000000..7793ca5d761 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/IndexMetadata.java @@ -0,0 +1,26 @@ +// Generated automatically from javax.jdo.metadata.IndexMetadata for testing purposes + +package javax.jdo.metadata; + +import javax.jdo.metadata.ColumnMetadata; +import javax.jdo.metadata.FieldMetadata; +import javax.jdo.metadata.MemberMetadata; +import javax.jdo.metadata.Metadata; +import javax.jdo.metadata.PropertyMetadata; + +public interface IndexMetadata extends Metadata +{ + ColumnMetadata newColumn(); + ColumnMetadata[] getColumns(); + FieldMetadata newFieldMetadata(String p0); + IndexMetadata setName(String p0); + IndexMetadata setTable(String p0); + IndexMetadata setUnique(boolean p0); + MemberMetadata[] getMembers(); + PropertyMetadata newPropertyMetadata(String p0); + String getName(); + String getTable(); + boolean getUnique(); + int getNumberOfColumns(); + int getNumberOfMembers(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/Indexed.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/Indexed.java new file mode 100644 index 00000000000..dbb0f0f8272 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/Indexed.java @@ -0,0 +1,10 @@ +// Generated automatically from javax.jdo.metadata.Indexed for testing purposes + +package javax.jdo.metadata; + + +public enum Indexed +{ + FALSE, TRUE, UNIQUE, UNSPECIFIED; + private Indexed() {} +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/InheritanceMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/InheritanceMetadata.java new file mode 100644 index 00000000000..2f3353db045 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/InheritanceMetadata.java @@ -0,0 +1,20 @@ +// Generated automatically from javax.jdo.metadata.InheritanceMetadata for testing purposes + +package javax.jdo.metadata; + +import javax.jdo.annotations.InheritanceStrategy; +import javax.jdo.metadata.DiscriminatorMetadata; +import javax.jdo.metadata.JoinMetadata; +import javax.jdo.metadata.Metadata; + +public interface InheritanceMetadata extends Metadata +{ + DiscriminatorMetadata getDiscriminatorMetadata(); + DiscriminatorMetadata newDiscriminatorMetadata(); + InheritanceMetadata setCustomStrategy(String p0); + InheritanceMetadata setStrategy(InheritanceStrategy p0); + InheritanceStrategy getStrategy(); + JoinMetadata getJoinMetadata(); + JoinMetadata newJoinMetadata(); + String getCustomStrategy(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/InterfaceMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/InterfaceMetadata.java new file mode 100644 index 00000000000..a9a80d2fb8a --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/InterfaceMetadata.java @@ -0,0 +1,9 @@ +// Generated automatically from javax.jdo.metadata.InterfaceMetadata for testing purposes + +package javax.jdo.metadata; + +import javax.jdo.metadata.TypeMetadata; + +public interface InterfaceMetadata extends TypeMetadata +{ +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/JDOMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/JDOMetadata.java new file mode 100644 index 00000000000..c18a83dd1a5 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/JDOMetadata.java @@ -0,0 +1,30 @@ +// Generated automatically from javax.jdo.metadata.JDOMetadata for testing purposes + +package javax.jdo.metadata; + +import javax.jdo.metadata.ClassMetadata; +import javax.jdo.metadata.FetchPlanMetadata; +import javax.jdo.metadata.InterfaceMetadata; +import javax.jdo.metadata.Metadata; +import javax.jdo.metadata.PackageMetadata; +import javax.jdo.metadata.QueryMetadata; + +public interface JDOMetadata extends Metadata +{ + ClassMetadata newClassMetadata(Class p0); + FetchPlanMetadata newFetchPlanMetadata(String p0); + FetchPlanMetadata[] getFetchPlans(); + InterfaceMetadata newInterfaceMetadata(Class p0); + JDOMetadata setCatalog(String p0); + JDOMetadata setSchema(String p0); + PackageMetadata newPackageMetadata(Package p0); + PackageMetadata newPackageMetadata(String p0); + PackageMetadata[] getPackages(); + QueryMetadata newQueryMetadata(String p0); + QueryMetadata[] getQueries(); + String getCatalog(); + String getSchema(); + int getNumberOfFetchPlans(); + int getNumberOfPackages(); + int getNumberOfQueries(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/JoinMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/JoinMetadata.java new file mode 100644 index 00000000000..2b59f363a83 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/JoinMetadata.java @@ -0,0 +1,39 @@ +// Generated automatically from javax.jdo.metadata.JoinMetadata for testing purposes + +package javax.jdo.metadata; + +import javax.jdo.annotations.ForeignKeyAction; +import javax.jdo.metadata.ColumnMetadata; +import javax.jdo.metadata.ForeignKeyMetadata; +import javax.jdo.metadata.IndexMetadata; +import javax.jdo.metadata.Indexed; +import javax.jdo.metadata.Metadata; +import javax.jdo.metadata.PrimaryKeyMetadata; +import javax.jdo.metadata.UniqueMetadata; + +public interface JoinMetadata extends Metadata +{ + Boolean getUnique(); + ColumnMetadata newColumnMetadata(); + ColumnMetadata[] getColumns(); + ForeignKeyAction getDeleteAction(); + ForeignKeyMetadata getForeignKeyMetadata(); + ForeignKeyMetadata newForeignKeyMetadata(); + IndexMetadata getIndexMetadata(); + IndexMetadata newIndexMetadata(); + Indexed getIndexed(); + JoinMetadata setColumn(String p0); + JoinMetadata setDeleteAction(ForeignKeyAction p0); + JoinMetadata setIndexed(Indexed p0); + JoinMetadata setOuter(boolean p0); + JoinMetadata setTable(String p0); + JoinMetadata setUnique(boolean p0); + PrimaryKeyMetadata getPrimaryKeyMetadata(); + PrimaryKeyMetadata newPrimaryKeyMetadata(); + String getColumn(); + String getTable(); + UniqueMetadata getUniqueMetadata(); + UniqueMetadata newUniqueMetadata(); + boolean getOuter(); + int getNumberOfColumns(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/KeyMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/KeyMetadata.java new file mode 100644 index 00000000000..d1fc5b74bf2 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/KeyMetadata.java @@ -0,0 +1,39 @@ +// Generated automatically from javax.jdo.metadata.KeyMetadata for testing purposes + +package javax.jdo.metadata; + +import javax.jdo.AttributeConverter; +import javax.jdo.annotations.ForeignKeyAction; +import javax.jdo.metadata.ColumnMetadata; +import javax.jdo.metadata.EmbeddedMetadata; +import javax.jdo.metadata.ForeignKeyMetadata; +import javax.jdo.metadata.IndexMetadata; +import javax.jdo.metadata.Metadata; +import javax.jdo.metadata.UniqueMetadata; + +public interface KeyMetadata extends Metadata +{ + AttributeConverter getConverter(); + Boolean getUseDefaultConversion(); + ColumnMetadata newColumnMetadata(); + ColumnMetadata[] getColumns(); + EmbeddedMetadata getEmbeddedMetadata(); + EmbeddedMetadata newEmbeddedMetadata(); + ForeignKeyAction getDeleteAction(); + ForeignKeyAction getUpdateAction(); + ForeignKeyMetadata getForeignKeyMetadata(); + ForeignKeyMetadata newForeignKeyMetadata(); + IndexMetadata getIndexMetadata(); + IndexMetadata newIndexMetadata(); + KeyMetadata setColumn(String p0); + KeyMetadata setConverter(AttributeConverter p0); + KeyMetadata setDeleteAction(ForeignKeyAction p0); + KeyMetadata setTable(String p0); + KeyMetadata setUpdateAction(ForeignKeyAction p0); + KeyMetadata setUseDefaultConversion(Boolean p0); + String getColumn(); + String getTable(); + UniqueMetadata getUniqueMetadata(); + UniqueMetadata newUniqueMetadata(); + int getNumberOfColumns(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/MapMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/MapMetadata.java new file mode 100644 index 00000000000..cdbb12ea0be --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/MapMetadata.java @@ -0,0 +1,25 @@ +// Generated automatically from javax.jdo.metadata.MapMetadata for testing purposes + +package javax.jdo.metadata; + +import javax.jdo.metadata.Metadata; + +public interface MapMetadata extends Metadata +{ + Boolean getDependentKey(); + Boolean getDependentValue(); + Boolean getEmbeddedKey(); + Boolean getEmbeddedValue(); + Boolean getSerializedKey(); + Boolean getSerializedValue(); + MapMetadata setDependentKey(boolean p0); + MapMetadata setDependentValue(boolean p0); + MapMetadata setEmbeddedKey(boolean p0); + MapMetadata setEmbeddedValue(boolean p0); + MapMetadata setKeyType(String p0); + MapMetadata setSerializedKey(boolean p0); + MapMetadata setSerializedValue(boolean p0); + MapMetadata setValueType(String p0); + String getKeyType(); + String getValueType(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/MemberMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/MemberMetadata.java new file mode 100644 index 00000000000..f524eae19f6 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/MemberMetadata.java @@ -0,0 +1,100 @@ +// Generated automatically from javax.jdo.metadata.MemberMetadata for testing purposes + +package javax.jdo.metadata; + +import javax.jdo.AttributeConverter; +import javax.jdo.annotations.ForeignKeyAction; +import javax.jdo.annotations.IdGeneratorStrategy; +import javax.jdo.annotations.NullValue; +import javax.jdo.annotations.PersistenceModifier; +import javax.jdo.metadata.ArrayMetadata; +import javax.jdo.metadata.CollectionMetadata; +import javax.jdo.metadata.ColumnMetadata; +import javax.jdo.metadata.ElementMetadata; +import javax.jdo.metadata.EmbeddedMetadata; +import javax.jdo.metadata.ForeignKeyMetadata; +import javax.jdo.metadata.IndexMetadata; +import javax.jdo.metadata.JoinMetadata; +import javax.jdo.metadata.KeyMetadata; +import javax.jdo.metadata.MapMetadata; +import javax.jdo.metadata.Metadata; +import javax.jdo.metadata.OrderMetadata; +import javax.jdo.metadata.UniqueMetadata; +import javax.jdo.metadata.ValueMetadata; + +public interface MemberMetadata extends Metadata +{ + ArrayMetadata getArrayMetadata(); + ArrayMetadata newArrayMetadata(); + AttributeConverter getConverter(); + Boolean getDefaultFetchGroup(); + Boolean getDependent(); + Boolean getEmbedded(); + Boolean getIndexed(); + Boolean getSerialized(); + Boolean getUnique(); + Boolean getUseDefaultConversion(); + CollectionMetadata getCollectionMetadata(); + CollectionMetadata newCollectionMetadata(); + ColumnMetadata newColumnMetadata(); + ColumnMetadata[] getColumns(); + ElementMetadata getElementMetadata(); + ElementMetadata newElementMetadata(); + EmbeddedMetadata getEmbeddedMetadata(); + EmbeddedMetadata newEmbeddedMetadata(); + ForeignKeyAction getDeleteAction(); + ForeignKeyMetadata getForeignKeyMetadata(); + ForeignKeyMetadata newForeignKeyMetadata(); + IdGeneratorStrategy getValueStrategy(); + IndexMetadata getIndexMetadata(); + IndexMetadata newIndexMetadata(); + JoinMetadata getJoinMetadata(); + JoinMetadata newJoinMetadata(); + KeyMetadata getKeyMetadata(); + KeyMetadata newKeyMetadata(); + MapMetadata getMapMetadata(); + MapMetadata newMapMetadata(); + MemberMetadata setCacheable(boolean p0); + MemberMetadata setColumn(String p0); + MemberMetadata setConverter(AttributeConverter p0); + MemberMetadata setCustomStrategy(String p0); + MemberMetadata setDefaultFetchGroup(boolean p0); + MemberMetadata setDeleteAction(ForeignKeyAction p0); + MemberMetadata setDependent(boolean p0); + MemberMetadata setEmbedded(boolean p0); + MemberMetadata setFieldType(String p0); + MemberMetadata setIndexed(boolean p0); + MemberMetadata setLoadFetchGroup(String p0); + MemberMetadata setMappedBy(String p0); + MemberMetadata setName(String p0); + MemberMetadata setNullValue(NullValue p0); + MemberMetadata setPersistenceModifier(PersistenceModifier p0); + MemberMetadata setPrimaryKey(boolean p0); + MemberMetadata setRecursionDepth(int p0); + MemberMetadata setSequence(String p0); + MemberMetadata setSerialized(boolean p0); + MemberMetadata setTable(String p0); + MemberMetadata setUnique(boolean p0); + MemberMetadata setUseDefaultConversion(Boolean p0); + MemberMetadata setValueStrategy(IdGeneratorStrategy p0); + NullValue getNullValue(); + OrderMetadata getOrderMetadata(); + OrderMetadata newOrderMetadata(); + PersistenceModifier getPersistenceModifier(); + String getColumn(); + String getCustomStrategy(); + String getFieldType(); + String getLoadFetchGroup(); + String getMappedBy(); + String getName(); + String getSequence(); + String getTable(); + UniqueMetadata getUniqueMetadata(); + UniqueMetadata newUniqueMetadata(); + ValueMetadata getValueMetadata(); + ValueMetadata newValueMetadata(); + boolean getCacheable(); + boolean getPrimaryKey(); + int getNumberOfColumns(); + int getRecursionDepth(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/Metadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/Metadata.java new file mode 100644 index 00000000000..c1a341710fb --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/Metadata.java @@ -0,0 +1,13 @@ +// Generated automatically from javax.jdo.metadata.Metadata for testing purposes + +package javax.jdo.metadata; + +import javax.jdo.metadata.ExtensionMetadata; + +public interface Metadata +{ + ExtensionMetadata newExtensionMetadata(String p0, String p1, String p2); + ExtensionMetadata[] getExtensions(); + Metadata getParent(); + int getNumberOfExtensions(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/OrderMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/OrderMetadata.java new file mode 100644 index 00000000000..f6d394cf22e --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/OrderMetadata.java @@ -0,0 +1,20 @@ +// Generated automatically from javax.jdo.metadata.OrderMetadata for testing purposes + +package javax.jdo.metadata; + +import javax.jdo.metadata.ColumnMetadata; +import javax.jdo.metadata.IndexMetadata; +import javax.jdo.metadata.Metadata; + +public interface OrderMetadata extends Metadata +{ + ColumnMetadata newColumnMetadata(); + ColumnMetadata[] getColumns(); + IndexMetadata getIndexMetadata(); + IndexMetadata newIndexMetadata(); + OrderMetadata setColumn(String p0); + OrderMetadata setMappedBy(String p0); + String getColumn(); + String getMappedBy(); + int getNumberOfColumns(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/PackageMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/PackageMetadata.java new file mode 100644 index 00000000000..3a4de4cb76d --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/PackageMetadata.java @@ -0,0 +1,29 @@ +// Generated automatically from javax.jdo.metadata.PackageMetadata for testing purposes + +package javax.jdo.metadata; + +import javax.jdo.annotations.SequenceStrategy; +import javax.jdo.metadata.ClassMetadata; +import javax.jdo.metadata.InterfaceMetadata; +import javax.jdo.metadata.Metadata; +import javax.jdo.metadata.SequenceMetadata; + +public interface PackageMetadata extends Metadata +{ + ClassMetadata newClassMetadata(Class p0); + ClassMetadata newClassMetadata(String p0); + ClassMetadata[] getClasses(); + InterfaceMetadata newInterfaceMetadata(Class p0); + InterfaceMetadata newInterfaceMetadata(String p0); + InterfaceMetadata[] getInterfaces(); + PackageMetadata setCatalog(String p0); + PackageMetadata setSchema(String p0); + SequenceMetadata newSequenceMetadata(String p0, SequenceStrategy p1); + SequenceMetadata[] getSequences(); + String getCatalog(); + String getName(); + String getSchema(); + int getNumberOfClasses(); + int getNumberOfInterfaces(); + int getNumberOfSequences(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/PrimaryKeyMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/PrimaryKeyMetadata.java new file mode 100644 index 00000000000..75bf6c5622e --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/PrimaryKeyMetadata.java @@ -0,0 +1,17 @@ +// Generated automatically from javax.jdo.metadata.PrimaryKeyMetadata for testing purposes + +package javax.jdo.metadata; + +import javax.jdo.metadata.ColumnMetadata; +import javax.jdo.metadata.Metadata; + +public interface PrimaryKeyMetadata extends Metadata +{ + ColumnMetadata newColumnMetadata(); + ColumnMetadata[] getColumns(); + PrimaryKeyMetadata setColumn(String p0); + PrimaryKeyMetadata setName(String p0); + String getColumn(); + String getName(); + int getNumberOfColumns(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/PropertyMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/PropertyMetadata.java new file mode 100644 index 00000000000..7842067e2cf --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/PropertyMetadata.java @@ -0,0 +1,11 @@ +// Generated automatically from javax.jdo.metadata.PropertyMetadata for testing purposes + +package javax.jdo.metadata; + +import javax.jdo.metadata.MemberMetadata; + +public interface PropertyMetadata extends MemberMetadata +{ + PropertyMetadata setFieldName(String p0); + String getFieldName(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/QueryMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/QueryMetadata.java new file mode 100644 index 00000000000..c638b9e6180 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/QueryMetadata.java @@ -0,0 +1,22 @@ +// Generated automatically from javax.jdo.metadata.QueryMetadata for testing purposes + +package javax.jdo.metadata; + +import javax.jdo.metadata.Metadata; + +public interface QueryMetadata extends Metadata +{ + Boolean getUnique(); + QueryMetadata setFetchPlan(String p0); + QueryMetadata setLanguage(String p0); + QueryMetadata setQuery(String p0); + QueryMetadata setResultClass(String p0); + QueryMetadata setUnique(boolean p0); + QueryMetadata setUnmodifiable(); + String getFetchPlan(); + String getLanguage(); + String getName(); + String getQuery(); + String getResultClass(); + boolean getUnmodifiable(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/SequenceMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/SequenceMetadata.java new file mode 100644 index 00000000000..5f4351c5128 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/SequenceMetadata.java @@ -0,0 +1,20 @@ +// Generated automatically from javax.jdo.metadata.SequenceMetadata for testing purposes + +package javax.jdo.metadata; + +import javax.jdo.annotations.SequenceStrategy; +import javax.jdo.metadata.Metadata; + +public interface SequenceMetadata extends Metadata +{ + Integer getAllocationSize(); + Integer getInitialValue(); + SequenceMetadata setAllocationSize(int p0); + SequenceMetadata setDatastoreSequence(String p0); + SequenceMetadata setFactoryClass(String p0); + SequenceMetadata setInitialValue(int p0); + SequenceStrategy getSequenceStrategy(); + String getDatastoreSequence(); + String getFactoryClass(); + String getName(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/TypeMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/TypeMetadata.java new file mode 100644 index 00000000000..264b7fac34d --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/TypeMetadata.java @@ -0,0 +1,78 @@ +// Generated automatically from javax.jdo.metadata.TypeMetadata for testing purposes + +package javax.jdo.metadata; + +import java.lang.reflect.Method; +import javax.jdo.annotations.IdentityType; +import javax.jdo.metadata.ColumnMetadata; +import javax.jdo.metadata.DatastoreIdentityMetadata; +import javax.jdo.metadata.FetchGroupMetadata; +import javax.jdo.metadata.ForeignKeyMetadata; +import javax.jdo.metadata.IndexMetadata; +import javax.jdo.metadata.InheritanceMetadata; +import javax.jdo.metadata.JoinMetadata; +import javax.jdo.metadata.MemberMetadata; +import javax.jdo.metadata.Metadata; +import javax.jdo.metadata.PrimaryKeyMetadata; +import javax.jdo.metadata.PropertyMetadata; +import javax.jdo.metadata.QueryMetadata; +import javax.jdo.metadata.UniqueMetadata; +import javax.jdo.metadata.VersionMetadata; + +public interface TypeMetadata extends Metadata +{ + Boolean getEmbeddedOnly(); + ColumnMetadata newColumnMetadata(); + ColumnMetadata[] getColumns(); + DatastoreIdentityMetadata getDatastoreIdentityMetadata(); + DatastoreIdentityMetadata newDatastoreIdentityMetadata(); + FetchGroupMetadata newFetchGroupMetadata(String p0); + FetchGroupMetadata[] getFetchGroups(); + ForeignKeyMetadata newForeignKeyMetadata(); + ForeignKeyMetadata[] getForeignKeys(); + IdentityType getIdentityType(); + IndexMetadata newIndexMetadata(); + IndexMetadata[] getIndices(); + InheritanceMetadata getInheritanceMetadata(); + InheritanceMetadata newInheritanceMetadata(); + JoinMetadata newJoinMetadata(); + JoinMetadata[] getJoins(); + MemberMetadata[] getMembers(); + PrimaryKeyMetadata getPrimaryKeyMetadata(); + PrimaryKeyMetadata newPrimaryKeyMetadata(); + PropertyMetadata newPropertyMetadata(Method p0); + PropertyMetadata newPropertyMetadata(String p0); + QueryMetadata newQueryMetadata(String p0); + QueryMetadata[] getQueries(); + String getCatalog(); + String getName(); + String getObjectIdClass(); + String getSchema(); + String getTable(); + TypeMetadata setCacheable(boolean p0); + TypeMetadata setCatalog(String p0); + TypeMetadata setDetachable(boolean p0); + TypeMetadata setEmbeddedOnly(boolean p0); + TypeMetadata setIdentityType(IdentityType p0); + TypeMetadata setObjectIdClass(String p0); + TypeMetadata setRequiresExtent(boolean p0); + TypeMetadata setSchema(String p0); + TypeMetadata setSerializeRead(boolean p0); + TypeMetadata setTable(String p0); + UniqueMetadata newUniqueMetadata(); + UniqueMetadata[] getUniques(); + VersionMetadata getVersionMetadata(); + VersionMetadata newVersionMetadata(); + boolean getCacheable(); + boolean getDetachable(); + boolean getRequiresExtent(); + boolean getSerializeRead(); + int getNumberOfColumns(); + int getNumberOfFetchGroups(); + int getNumberOfForeignKeys(); + int getNumberOfIndices(); + int getNumberOfJoins(); + int getNumberOfMembers(); + int getNumberOfQueries(); + int getNumberOfUniques(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/UniqueMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/UniqueMetadata.java new file mode 100644 index 00000000000..ade512b2c3b --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/UniqueMetadata.java @@ -0,0 +1,26 @@ +// Generated automatically from javax.jdo.metadata.UniqueMetadata for testing purposes + +package javax.jdo.metadata; + +import javax.jdo.metadata.ColumnMetadata; +import javax.jdo.metadata.FieldMetadata; +import javax.jdo.metadata.MemberMetadata; +import javax.jdo.metadata.Metadata; +import javax.jdo.metadata.PropertyMetadata; + +public interface UniqueMetadata extends Metadata +{ + Boolean getDeferred(); + ColumnMetadata newColumnMetadata(); + ColumnMetadata[] getColumns(); + FieldMetadata newFieldMetadata(String p0); + MemberMetadata[] getMembers(); + PropertyMetadata newPropertyMetadata(String p0); + String getName(); + String getTable(); + UniqueMetadata setDeferred(boolean p0); + UniqueMetadata setName(String p0); + UniqueMetadata setTable(String p0); + int getNumberOfColumns(); + int getNumberOfMembers(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/ValueMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/ValueMetadata.java new file mode 100644 index 00000000000..1732926ee4b --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/ValueMetadata.java @@ -0,0 +1,38 @@ +// Generated automatically from javax.jdo.metadata.ValueMetadata for testing purposes + +package javax.jdo.metadata; + +import javax.jdo.AttributeConverter; +import javax.jdo.annotations.ForeignKeyAction; +import javax.jdo.metadata.ColumnMetadata; +import javax.jdo.metadata.EmbeddedMetadata; +import javax.jdo.metadata.ForeignKeyMetadata; +import javax.jdo.metadata.IndexMetadata; +import javax.jdo.metadata.Metadata; +import javax.jdo.metadata.UniqueMetadata; + +public interface ValueMetadata extends Metadata +{ + AttributeConverter getConverter(); + Boolean getUseDefaultConversion(); + ColumnMetadata newColumnMetadata(); + ColumnMetadata[] getColumns(); + EmbeddedMetadata getEmbeddedMetadata(); + EmbeddedMetadata newEmbeddedMetadata(); + ForeignKeyAction getDeleteAction(); + ForeignKeyAction getUpdateAction(); + ForeignKeyMetadata getForeignKeyMetadata(); + ForeignKeyMetadata newForeignKeyMetadata(); + IndexMetadata getIndexMetadata(); + IndexMetadata newIndexMetadata(); + String getColumn(); + String getTable(); + UniqueMetadata getUniqueMetadata(); + UniqueMetadata newUniqueMetadata(); + ValueMetadata setColumn(String p0); + ValueMetadata setConverter(AttributeConverter p0); + ValueMetadata setDeleteAction(ForeignKeyAction p0); + ValueMetadata setTable(String p0); + ValueMetadata setUpdateAction(ForeignKeyAction p0); + ValueMetadata setUseDefaultConversion(Boolean p0); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/metadata/VersionMetadata.java b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/VersionMetadata.java new file mode 100644 index 00000000000..28f240eeba8 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/metadata/VersionMetadata.java @@ -0,0 +1,24 @@ +// Generated automatically from javax.jdo.metadata.VersionMetadata for testing purposes + +package javax.jdo.metadata; + +import javax.jdo.annotations.VersionStrategy; +import javax.jdo.metadata.ColumnMetadata; +import javax.jdo.metadata.IndexMetadata; +import javax.jdo.metadata.Indexed; +import javax.jdo.metadata.Metadata; + +public interface VersionMetadata extends Metadata +{ + ColumnMetadata newColumnMetadata(); + ColumnMetadata[] getColumns(); + IndexMetadata getIndexMetadata(); + IndexMetadata newIndexMetadata(); + Indexed getIndexed(); + String getColumn(); + VersionMetadata setColumn(String p0); + VersionMetadata setIndexed(Indexed p0); + VersionMetadata setStrategy(VersionStrategy p0); + VersionStrategy getStrategy(); + int getNumberOfColumns(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/query/BooleanExpression.java b/java/ql/test/stubs/apache-hive/javax/jdo/query/BooleanExpression.java new file mode 100644 index 00000000000..5d7db14437e --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/query/BooleanExpression.java @@ -0,0 +1,13 @@ +// Generated automatically from javax.jdo.query.BooleanExpression for testing purposes + +package javax.jdo.query; + +import javax.jdo.query.ComparableExpression; + +public interface BooleanExpression extends ComparableExpression +{ + BooleanExpression and(BooleanExpression p0); + BooleanExpression neg(); + BooleanExpression not(); + BooleanExpression or(BooleanExpression p0); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/query/CharacterExpression.java b/java/ql/test/stubs/apache-hive/javax/jdo/query/CharacterExpression.java new file mode 100644 index 00000000000..46a06a2475a --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/query/CharacterExpression.java @@ -0,0 +1,13 @@ +// Generated automatically from javax.jdo.query.CharacterExpression for testing purposes + +package javax.jdo.query; + +import javax.jdo.query.ComparableExpression; + +public interface CharacterExpression extends ComparableExpression +{ + CharacterExpression com(); + CharacterExpression neg(); + CharacterExpression toLowerCase(); + CharacterExpression toUpperCase(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/query/CollectionExpression.java b/java/ql/test/stubs/apache-hive/javax/jdo/query/CollectionExpression.java new file mode 100644 index 00000000000..93a761bed18 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/query/CollectionExpression.java @@ -0,0 +1,16 @@ +// Generated automatically from javax.jdo.query.CollectionExpression for testing purposes + +package javax.jdo.query; + +import java.util.Collection; +import javax.jdo.query.BooleanExpression; +import javax.jdo.query.Expression; +import javax.jdo.query.NumericExpression; + +public interface CollectionExpression, E> extends javax.jdo.query.Expression +{ + BooleanExpression contains(E p0); + BooleanExpression contains(javax.jdo.query.Expression p0); + BooleanExpression isEmpty(); + NumericExpression size(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/query/ComparableExpression.java b/java/ql/test/stubs/apache-hive/javax/jdo/query/ComparableExpression.java new file mode 100644 index 00000000000..ec6226b9c50 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/query/ComparableExpression.java @@ -0,0 +1,24 @@ +// Generated automatically from javax.jdo.query.ComparableExpression for testing purposes + +package javax.jdo.query; + +import javax.jdo.query.BooleanExpression; +import javax.jdo.query.Expression; +import javax.jdo.query.NumericExpression; +import javax.jdo.query.OrderExpression; + +public interface ComparableExpression extends javax.jdo.query.Expression +{ + BooleanExpression gt(ComparableExpression p0); + BooleanExpression gt(T p0); + BooleanExpression gteq(ComparableExpression p0); + BooleanExpression gteq(T p0); + BooleanExpression lt(ComparableExpression p0); + BooleanExpression lt(T p0); + BooleanExpression lteq(ComparableExpression p0); + BooleanExpression lteq(T p0); + NumericExpression max(); + NumericExpression min(); + OrderExpression asc(); + OrderExpression desc(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/query/DateExpression.java b/java/ql/test/stubs/apache-hive/javax/jdo/query/DateExpression.java new file mode 100644 index 00000000000..f913ddc3e9c --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/query/DateExpression.java @@ -0,0 +1,14 @@ +// Generated automatically from javax.jdo.query.DateExpression for testing purposes + +package javax.jdo.query; + +import java.sql.Date; +import javax.jdo.query.ComparableExpression; +import javax.jdo.query.NumericExpression; + +public interface DateExpression extends javax.jdo.query.ComparableExpression +{ + NumericExpression getDay(); + NumericExpression getMonth(); + NumericExpression getYear(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/query/DateTimeExpression.java b/java/ql/test/stubs/apache-hive/javax/jdo/query/DateTimeExpression.java new file mode 100644 index 00000000000..5f1c7e13c34 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/query/DateTimeExpression.java @@ -0,0 +1,17 @@ +// Generated automatically from javax.jdo.query.DateTimeExpression for testing purposes + +package javax.jdo.query; + +import java.util.Date; +import javax.jdo.query.ComparableExpression; +import javax.jdo.query.NumericExpression; + +public interface DateTimeExpression extends javax.jdo.query.ComparableExpression +{ + NumericExpression getDay(); + NumericExpression getHour(); + NumericExpression getMinute(); + NumericExpression getMonth(); + NumericExpression getSecond(); + NumericExpression getYear(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/query/Expression.java b/java/ql/test/stubs/apache-hive/javax/jdo/query/Expression.java new file mode 100644 index 00000000000..837ac2424e5 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/query/Expression.java @@ -0,0 +1,18 @@ +// Generated automatically from javax.jdo.query.Expression for testing purposes + +package javax.jdo.query; + +import javax.jdo.query.BooleanExpression; +import javax.jdo.query.NumericExpression; + +public interface Expression +{ + BooleanExpression eq(Expression p0); + BooleanExpression eq(T p0); + BooleanExpression instanceOf(Class p0); + BooleanExpression ne(Expression p0); + BooleanExpression ne(T p0); + Expression cast(Class p0); + NumericExpression count(); + NumericExpression countDistinct(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/query/ListExpression.java b/java/ql/test/stubs/apache-hive/javax/jdo/query/ListExpression.java new file mode 100644 index 00000000000..d53f2937f88 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/query/ListExpression.java @@ -0,0 +1,15 @@ +// Generated automatically from javax.jdo.query.ListExpression for testing purposes + +package javax.jdo.query; + +import java.util.Collection; +import java.util.List; +import javax.jdo.query.CollectionExpression; +import javax.jdo.query.Expression; +import javax.jdo.query.NumericExpression; + +public interface ListExpression, E> extends CollectionExpression +{ + javax.jdo.query.Expression get(NumericExpression p0); + javax.jdo.query.Expression get(int p0); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/query/MapExpression.java b/java/ql/test/stubs/apache-hive/javax/jdo/query/MapExpression.java new file mode 100644 index 00000000000..b57622b90a1 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/query/MapExpression.java @@ -0,0 +1,20 @@ +// Generated automatically from javax.jdo.query.MapExpression for testing purposes + +package javax.jdo.query; + +import java.util.Map; +import javax.jdo.query.BooleanExpression; +import javax.jdo.query.Expression; +import javax.jdo.query.NumericExpression; + +public interface MapExpression, K, V> extends javax.jdo.query.Expression +{ + BooleanExpression containsEntry(Expression> p0); + BooleanExpression containsEntry(Map.Entry p0); + BooleanExpression containsKey(Expression p0); + BooleanExpression containsKey(K p0); + BooleanExpression containsValue(V p0); + BooleanExpression containsValue(javax.jdo.query.Expression p0); + BooleanExpression isEmpty(); + NumericExpression size(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/query/NumericExpression.java b/java/ql/test/stubs/apache-hive/javax/jdo/query/NumericExpression.java new file mode 100644 index 00000000000..c456f9fef90 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/query/NumericExpression.java @@ -0,0 +1,39 @@ +// Generated automatically from javax.jdo.query.NumericExpression for testing purposes + +package javax.jdo.query; + +import javax.jdo.query.ComparableExpression; +import javax.jdo.query.Expression; + +public interface NumericExpression extends ComparableExpression +{ + NumericExpression abs(); + NumericExpression acos(); + NumericExpression add(Expression p0); + NumericExpression add(Number p0); + NumericExpression asin(); + NumericExpression atan(); + NumericExpression avg(); + NumericExpression bAnd(NumericExpression p0); + NumericExpression bOr(NumericExpression p0); + NumericExpression bXor(NumericExpression p0); + NumericExpression ceil(); + NumericExpression com(); + NumericExpression cos(); + NumericExpression div(Expression p0); + NumericExpression div(Number p0); + NumericExpression exp(); + NumericExpression floor(); + NumericExpression log(); + NumericExpression mod(Expression p0); + NumericExpression mod(Number p0); + NumericExpression mul(Expression p0); + NumericExpression mul(Number p0); + NumericExpression neg(); + NumericExpression sin(); + NumericExpression sqrt(); + NumericExpression sub(Expression p0); + NumericExpression sub(Number p0); + NumericExpression sum(); + NumericExpression tan(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/query/OrderExpression.java b/java/ql/test/stubs/apache-hive/javax/jdo/query/OrderExpression.java new file mode 100644 index 00000000000..fe590a1b7a1 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/query/OrderExpression.java @@ -0,0 +1,16 @@ +// Generated automatically from javax.jdo.query.OrderExpression for testing purposes + +package javax.jdo.query; + +import javax.jdo.query.Expression; + +public interface OrderExpression +{ + OrderExpression.OrderDirection getDirection(); + javax.jdo.query.Expression getExpression(); + static public enum OrderDirection + { + ASC, DESC; + private OrderDirection() {} + } +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/query/PersistableExpression.java b/java/ql/test/stubs/apache-hive/javax/jdo/query/PersistableExpression.java new file mode 100644 index 00000000000..afc709de6f2 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/query/PersistableExpression.java @@ -0,0 +1,11 @@ +// Generated automatically from javax.jdo.query.PersistableExpression for testing purposes + +package javax.jdo.query; + +import javax.jdo.query.Expression; + +public interface PersistableExpression extends javax.jdo.query.Expression +{ + Expression jdoObjectId(); + Expression jdoVersion(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/query/StringExpression.java b/java/ql/test/stubs/apache-hive/javax/jdo/query/StringExpression.java new file mode 100644 index 00000000000..4e8a23b5502 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/query/StringExpression.java @@ -0,0 +1,38 @@ +// Generated automatically from javax.jdo.query.StringExpression for testing purposes + +package javax.jdo.query; + +import javax.jdo.query.BooleanExpression; +import javax.jdo.query.CharacterExpression; +import javax.jdo.query.ComparableExpression; +import javax.jdo.query.Expression; +import javax.jdo.query.NumericExpression; + +public interface StringExpression extends ComparableExpression +{ + BooleanExpression endsWith(String p0); + BooleanExpression endsWith(StringExpression p0); + BooleanExpression equalsIgnoreCase(String p0); + BooleanExpression equalsIgnoreCase(StringExpression p0); + BooleanExpression matches(String p0); + BooleanExpression matches(StringExpression p0); + BooleanExpression startsWith(String p0); + BooleanExpression startsWith(StringExpression p0); + CharacterExpression charAt(NumericExpression p0); + CharacterExpression charAt(int p0); + NumericExpression indexOf(String p0); + NumericExpression indexOf(String p0, NumericExpression p1); + NumericExpression indexOf(String p0, int p1); + NumericExpression indexOf(StringExpression p0); + NumericExpression indexOf(StringExpression p0, NumericExpression p1); + NumericExpression indexOf(StringExpression p0, int p1); + NumericExpression length(); + StringExpression add(Expression p0); + StringExpression substring(NumericExpression p0); + StringExpression substring(NumericExpression p0, NumericExpression p1); + StringExpression substring(int p0); + StringExpression substring(int p0, int p1); + StringExpression toLowerCase(); + StringExpression toUpperCase(); + StringExpression trim(); +} diff --git a/java/ql/test/stubs/apache-hive/javax/jdo/query/TimeExpression.java b/java/ql/test/stubs/apache-hive/javax/jdo/query/TimeExpression.java new file mode 100644 index 00000000000..6cce97021c1 --- /dev/null +++ b/java/ql/test/stubs/apache-hive/javax/jdo/query/TimeExpression.java @@ -0,0 +1,14 @@ +// Generated automatically from javax.jdo.query.TimeExpression for testing purposes + +package javax.jdo.query; + +import java.sql.Time; +import javax.jdo.query.ComparableExpression; +import javax.jdo.query.NumericExpression; + +public interface TimeExpression extends ComparableExpression